Compare commits
55 Commits
2.0-beta.4
...
2.0-beta.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d57a74667 | ||
|
|
21b0029df7 | ||
|
|
c0ccafa787 | ||
|
|
d63ad9ea8f | ||
|
|
8f56a60fb1 | ||
|
|
358ae46627 | ||
|
|
0b1db3c40c | ||
|
|
0ad7e659d2 | ||
|
|
58c3875dc7 | ||
|
|
84f506f1fe | ||
|
|
d0a3ccc76a | ||
|
|
93ac9215c2 | ||
|
|
a6b501a62c | ||
|
|
e956bacf07 | ||
|
|
60b6b283b1 | ||
|
|
1c9b390551 | ||
|
|
21a487635d | ||
|
|
c92a26d5e6 | ||
|
|
49b514b8f3 | ||
|
|
5cb835c536 | ||
|
|
aa8f2d230d | ||
|
|
2fa913b09a | ||
|
|
de598c70a7 | ||
|
|
baecdeff24 | ||
|
|
d4b7d22b5f | ||
|
|
87f79737e8 | ||
|
|
9f0ffe13f8 | ||
|
|
adaa31d76c | ||
|
|
b051d764f8 | ||
|
|
ffb5b4a4f3 | ||
|
|
fd87b7fb75 | ||
|
|
f035902842 | ||
|
|
dbc5f7253f | ||
|
|
f0e7253374 | ||
|
|
ffe37bf907 | ||
|
|
a63e2099d3 | ||
|
|
0308e4e7a7 | ||
|
|
43f8df09f0 | ||
|
|
b6ed9ffb74 | ||
|
|
a84da7d8a5 | ||
|
|
249aebea0f | ||
|
|
11a527481e | ||
|
|
8d729216b5 | ||
|
|
1677e8c424 | ||
|
|
987aacbb67 | ||
|
|
2714d0b9d5 | ||
|
|
1d98c41168 | ||
|
|
e42ec4b862 | ||
|
|
59fc5ab851 | ||
|
|
5d14c392e8 | ||
|
|
c6ca8c1fa4 | ||
|
|
28307e26c3 | ||
|
|
6d42eb62f0 | ||
|
|
877c9ca099 | ||
|
|
9b652fc133 |
@@ -11,14 +11,14 @@ accounts-google@1.4.0
|
|||||||
email@2.2.1
|
email@2.2.1
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mongo@1.15.0
|
mongo@1.16.0
|
||||||
session@1.2.0
|
session@1.2.0
|
||||||
tracker@1.2.0
|
tracker@1.2.0
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
reload@1.3.1
|
reload@1.3.1
|
||||||
ejson@1.1.2
|
ejson@1.1.2
|
||||||
check@1.3.1
|
check@1.3.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.1
|
||||||
shell-server@0.5.0
|
shell-server@0.5.0
|
||||||
ecmascript@0.16.2
|
ecmascript@0.16.2
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
@@ -48,3 +48,4 @@ simple:rest-bearer-token-parser
|
|||||||
simple:rest-json-error-handler
|
simple:rest-json-error-handler
|
||||||
littledata:synced-cron
|
littledata:synced-cron
|
||||||
mdg:meteor-apm-agent
|
mdg:meteor-apm-agent
|
||||||
|
typescript@4.5.4
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
METEOR@2.7.3
|
METEOR@2.8.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
accounts-base@2.2.3
|
accounts-base@2.2.4
|
||||||
accounts-google@1.4.0
|
accounts-google@1.4.0
|
||||||
accounts-oauth@1.4.1
|
accounts-oauth@1.4.1
|
||||||
accounts-password@2.3.1
|
accounts-password@2.3.1
|
||||||
@@ -12,7 +12,7 @@ aldeed:collection2@3.5.0
|
|||||||
aldeed:schema-index@3.0.0
|
aldeed:schema-index@3.0.0
|
||||||
allow-deny@1.1.1
|
allow-deny@1.1.1
|
||||||
autoupdate@1.8.0
|
autoupdate@1.8.0
|
||||||
babel-compiler@7.9.0
|
babel-compiler@7.9.2
|
||||||
babel-runtime@1.5.1
|
babel-runtime@1.5.1
|
||||||
base64@1.0.12
|
base64@1.0.12
|
||||||
binary-heap@1.0.11
|
binary-heap@1.0.11
|
||||||
@@ -27,10 +27,10 @@ coffeescript@2.4.1
|
|||||||
coffeescript-compiler@2.4.1
|
coffeescript-compiler@2.4.1
|
||||||
dburles:mongo-collection-instances@0.3.6
|
dburles:mongo-collection-instances@0.3.6
|
||||||
ddp@1.4.0
|
ddp@1.4.0
|
||||||
ddp-client@2.5.0
|
ddp-client@2.6.0
|
||||||
ddp-common@1.4.0
|
ddp-common@1.4.0
|
||||||
ddp-rate-limiter@1.1.0
|
ddp-rate-limiter@1.1.0
|
||||||
ddp-server@2.5.0
|
ddp-server@2.6.0
|
||||||
diff-sequence@1.1.1
|
diff-sequence@1.1.1
|
||||||
dynamic-import@0.7.2
|
dynamic-import@0.7.2
|
||||||
ecmascript@0.16.2
|
ecmascript@0.16.2
|
||||||
@@ -55,33 +55,33 @@ littledata:synced-cron@1.5.1
|
|||||||
livedata@1.0.18
|
livedata@1.0.18
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
mdg:meteor-apm-agent@3.5.0
|
mdg:meteor-apm-agent@3.5.1
|
||||||
mdg:validated-method@1.2.0
|
mdg:validated-method@1.2.0
|
||||||
meteor@1.10.0
|
meteor@1.10.1
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
meteortesting:browser-tests@1.3.5
|
meteortesting:browser-tests@1.3.5
|
||||||
meteortesting:mocha@2.0.3
|
meteortesting:mocha@2.0.3
|
||||||
meteortesting:mocha-core@8.1.2
|
meteortesting:mocha-core@8.1.2
|
||||||
mikowals:batch-insert@1.3.0
|
mikowals:batch-insert@1.3.0
|
||||||
minifier-css@1.6.0
|
minifier-css@1.6.1
|
||||||
minifier-js@2.7.4
|
minifier-js@2.7.5
|
||||||
minimongo@1.8.0
|
minimongo@1.9.0
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mobile-status-bar@1.1.0
|
mobile-status-bar@1.1.0
|
||||||
modern-browsers@0.1.8
|
modern-browsers@0.1.8
|
||||||
modules@0.18.0
|
modules@0.19.0
|
||||||
modules-runtime@0.13.0
|
modules-runtime@0.13.0
|
||||||
mongo@1.15.0
|
mongo@1.16.0
|
||||||
mongo-decimal@0.1.3
|
mongo-decimal@0.1.3
|
||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
mongo-id@1.0.8
|
mongo-id@1.0.8
|
||||||
mongo-livedata@1.0.12
|
mongo-livedata@1.0.12
|
||||||
npm-mongo@4.3.1
|
npm-mongo@4.9.0
|
||||||
oauth@2.1.2
|
oauth@2.1.2
|
||||||
oauth2@1.3.1
|
oauth2@1.3.1
|
||||||
ordered-dict@1.1.0
|
ordered-dict@1.1.0
|
||||||
ostrio:cookies@2.7.2
|
ostrio:cookies@2.7.2
|
||||||
ostrio:files@2.0.1
|
ostrio:files@2.3.0
|
||||||
patreon-oauth@0.1.0
|
patreon-oauth@0.1.0
|
||||||
peerlibrary:assert@0.3.0
|
peerlibrary:assert@0.3.0
|
||||||
peerlibrary:check-extension@0.7.0
|
peerlibrary:check-extension@0.7.0
|
||||||
@@ -116,7 +116,7 @@ simple:rest-json-error-handler@1.1.1
|
|||||||
simple:rest-method-mixin@1.1.0
|
simple:rest-method-mixin@1.1.0
|
||||||
socket-stream-client@0.5.0
|
socket-stream-client@0.5.0
|
||||||
spacebars-compiler@1.3.1
|
spacebars-compiler@1.3.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.1
|
||||||
static-html@1.3.2
|
static-html@1.3.2
|
||||||
templating-tools@1.2.2
|
templating-tools@1.2.2
|
||||||
tmeasday:check-npm-versions@1.0.2
|
tmeasday:check-npm-versions@1.0.2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const damageProperty = new ValidatedMethod({
|
|||||||
run({ _id, operation, value }) {
|
run({ _id, operation, value }) {
|
||||||
|
|
||||||
// Get action context
|
// Get action context
|
||||||
const prop = CreatureProperties.findOne(_id);
|
let prop = CreatureProperties.findOne(_id);
|
||||||
if (!prop) throw new Meteor.Error(
|
if (!prop) throw new Meteor.Error(
|
||||||
'Damage property failed', 'Property doesn\'t exist'
|
'Damage property failed', 'Property doesn\'t exist'
|
||||||
);
|
);
|
||||||
@@ -43,6 +43,14 @@ const damageProperty = new ValidatedMethod({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the prop by its actionContext counterpart if possible
|
||||||
|
if (prop.variableName) {
|
||||||
|
const actionContextProp = actionContext.scope[prop.variableName];
|
||||||
|
if (actionContextProp?._id === prop._id) {
|
||||||
|
prop = actionContextProp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
||||||
|
|
||||||
// Insert the log
|
// Insert the log
|
||||||
@@ -94,6 +102,9 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
|
// Also write it straight to the prop so that it is updated in the actionContext
|
||||||
|
prop.damage = damage;
|
||||||
|
prop.value = newValue;
|
||||||
} else if (operation === 'increment') {
|
} else if (operation === 'increment') {
|
||||||
let currentValue = prop.value || 0;
|
let currentValue = prop.value || 0;
|
||||||
let currentDamage = prop.damage || 0;
|
let currentDamage = prop.damage || 0;
|
||||||
@@ -111,6 +122,9 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
|
// Also write it straight to the prop so that it is updated in the actionContext
|
||||||
|
prop.damage += increment;
|
||||||
|
prop.value -= increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ const flipToggle = new ValidatedMethod({
|
|||||||
|
|
||||||
// Invert the current value, disabled is the canonical store of value
|
// Invert the current value, disabled is the canonical store of value
|
||||||
const currentValue = !property.disabled;
|
const currentValue = !property.disabled;
|
||||||
CreatureProperties.update(_id, {$set: {
|
CreatureProperties.update(_id, {
|
||||||
|
$set: {
|
||||||
enabled: !currentValue,
|
enabled: !currentValue,
|
||||||
disabled: currentValue,
|
disabled: currentValue,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}}, {
|
}
|
||||||
|
}, {
|
||||||
selector: { type: 'toggle' },
|
selector: { type: 'toggle' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ let ExperienceSchema = new SimpleSchema({
|
|||||||
|
|
||||||
Experiences.attachSchema(ExperienceSchema);
|
Experiences.attachSchema(ExperienceSchema);
|
||||||
|
|
||||||
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
const insertExperienceForCreature = function ({ experience, creatureId }) {
|
||||||
if (experience.xp) {
|
if (experience.xp) {
|
||||||
Creatures.update(creatureId, {
|
Creatures.update(creatureId, {
|
||||||
$inc: { 'denormalizedStats.xp': experience.xp },
|
$inc: { 'denormalizedStats.xp': experience.xp },
|
||||||
@@ -172,11 +172,13 @@ const recomputeExperiences = new ValidatedMethod({
|
|||||||
xp += experience.xp || 0;
|
xp += experience.xp || 0;
|
||||||
milestoneLevels += experience.levels || 0;
|
milestoneLevels += experience.levels || 0;
|
||||||
});
|
});
|
||||||
Creatures.update(creatureId, {$set: {
|
Creatures.update(creatureId, {
|
||||||
|
$set: {
|
||||||
'denormalizedStats.xp': xp,
|
'denormalizedStats.xp': xp,
|
||||||
'denormalizedStats.milestoneLevels': milestoneLevels,
|
'denormalizedStats.milestoneLevels': milestoneLevels,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -99,14 +99,16 @@ const insertCreatureLog = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
run({ log }) {
|
run({ log }) {
|
||||||
const creatureId = log.creatureId;
|
const creatureId = log.creatureId;
|
||||||
const creature = Creatures.findOne(creatureId, {fields: {
|
const creature = Creatures.findOne(creatureId, {
|
||||||
|
fields: {
|
||||||
readers: 1,
|
readers: 1,
|
||||||
writers: 1,
|
writers: 1,
|
||||||
owner: 1,
|
owner: 1,
|
||||||
'settings.discordWebhook': 1,
|
'settings.discordWebhook': 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
// Build the new log
|
// Build the new log
|
||||||
let id = insertCreatureLogWork({ log, creature, method: this })
|
let id = insertCreatureLogWork({ log, creature, method: this })
|
||||||
@@ -154,14 +156,16 @@ const logRoll = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
}).validator(),
|
}).validator(),
|
||||||
run({ roll, creatureId }) {
|
run({ roll, creatureId }) {
|
||||||
const creature = Creatures.findOne(creatureId, {fields: {
|
const creature = Creatures.findOne(creatureId, {
|
||||||
|
fields: {
|
||||||
readers: 1,
|
readers: 1,
|
||||||
writers: 1,
|
writers: 1,
|
||||||
owner: 1,
|
owner: 1,
|
||||||
'settings.discordWebhook': 1,
|
'settings.discordWebhook': 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
||||||
let logContent = []
|
let logContent = []
|
||||||
|
|||||||
3
app/imports/api/docs/Docs.js
Normal file
3
app/imports/api/docs/Docs.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
if (Meteor.isServer) throw 'Client side only collection, don\'t import on server';
|
||||||
|
const Docs = new Mongo.Collection('docs');
|
||||||
|
export default Docs;
|
||||||
@@ -4,6 +4,7 @@ import branch from './applyPropertyByType/applyBranch.js';
|
|||||||
import buff from './applyPropertyByType/applyBuff.js';
|
import buff from './applyPropertyByType/applyBuff.js';
|
||||||
import buffRemover from './applyPropertyByType/applyBuffRemover.js';
|
import buffRemover from './applyPropertyByType/applyBuffRemover.js';
|
||||||
import damage from './applyPropertyByType/applyDamage.js';
|
import damage from './applyPropertyByType/applyDamage.js';
|
||||||
|
import folder from './applyPropertyByType/applyFolder.js';
|
||||||
import note from './applyPropertyByType/applyNote.js';
|
import note from './applyPropertyByType/applyNote.js';
|
||||||
import roll from './applyPropertyByType/applyRoll.js';
|
import roll from './applyPropertyByType/applyRoll.js';
|
||||||
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
||||||
@@ -16,6 +17,7 @@ const applyPropertyByType = {
|
|||||||
buff,
|
buff,
|
||||||
buffRemover,
|
buffRemover,
|
||||||
damage,
|
damage,
|
||||||
|
folder,
|
||||||
note,
|
note,
|
||||||
roll,
|
roll,
|
||||||
savingThrow,
|
savingThrow,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function applyAction(node, actionContext) {
|
|||||||
recalculateInlineCalculations(prop.summary, actionContext);
|
recalculateInlineCalculations(prop.summary, actionContext);
|
||||||
content.value = prop.summary.value;
|
content.value = prop.summary.value;
|
||||||
}
|
}
|
||||||
actionContext.addLog(content);
|
if (!prop.silent) actionContext.addLog(content);
|
||||||
|
|
||||||
// Spend the resources
|
// Spend the resources
|
||||||
const failed = spendResources(prop, actionContext);
|
const failed = spendResources(prop, actionContext);
|
||||||
@@ -188,7 +188,7 @@ function applyChildren(node, actionContext) {
|
|||||||
function spendResources(prop, actionContext){
|
function spendResources(prop, actionContext){
|
||||||
// Check Uses
|
// Check Uses
|
||||||
if (prop.usesLeft <= 0){
|
if (prop.usesLeft <= 0){
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||||
});
|
});
|
||||||
@@ -196,7 +196,7 @@ function spendResources(prop, actionContext){
|
|||||||
}
|
}
|
||||||
// Resources
|
// Resources
|
||||||
if (prop.insufficientResources){
|
if (prop.insufficientResources){
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||||
});
|
});
|
||||||
@@ -257,7 +257,7 @@ function spendResources(prop, actionContext){
|
|||||||
}, {
|
}, {
|
||||||
selector: prop
|
selector: prop
|
||||||
});
|
});
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Uses left',
|
name: 'Uses left',
|
||||||
value: prop.usesLeft - 1,
|
value: prop.usesLeft - 1,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -288,12 +288,12 @@ function spendResources(prop, actionContext){
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Log all the spending
|
// Log all the spending
|
||||||
if (gainLog.length) actionContext.addLog({
|
if (gainLog.length && !prop.silent) actionContext.addLog({
|
||||||
name: 'Gained',
|
name: 'Gained',
|
||||||
value: gainLog.join('\n'),
|
value: gainLog.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
});
|
});
|
||||||
if (spendLog.length) actionContext.addLog({
|
if (spendLog.length && !prop.silent) actionContext.addLog({
|
||||||
name: 'Spent',
|
name: 'Spent',
|
||||||
value: spendLog.join('\n'),
|
value: spendLog.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default function applyAdjustment(node, actionContext){
|
|||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
let stat = target.variables[prop.stat];
|
let stat = target.variables[prop.stat];
|
||||||
if (!stat?.type) {
|
if (!stat?.type) {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||||
});
|
});
|
||||||
@@ -36,7 +36,7 @@ export default function applyAdjustment(node, actionContext){
|
|||||||
value,
|
value,
|
||||||
actionContext,
|
actionContext,
|
||||||
});
|
});
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||||
` ${value}`,
|
` ${value}`,
|
||||||
@@ -44,7 +44,7 @@ export default function applyAdjustment(node, actionContext){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||||
` ${value}`,
|
` ${value}`,
|
||||||
|
|||||||
@@ -36,25 +36,25 @@ export default function applyBranch(node, actionContext){
|
|||||||
break;
|
break;
|
||||||
case 'hit':
|
case 'hit':
|
||||||
if (scope['$attackHit']?.value){
|
if (scope['$attackHit']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On hit**'});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On hit**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'miss':
|
case 'miss':
|
||||||
if (scope['$attackMiss']?.value){
|
if (scope['$attackMiss']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On miss**'});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On miss**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'failedSave':
|
case 'failedSave':
|
||||||
if (scope['$saveFailed']?.value){
|
if (scope['$saveFailed']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On failed save**'});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On failed save**'});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'successfulSave':
|
case 'successfulSave':
|
||||||
if (scope['$saveSucceeded']?.value){
|
if (scope['$saveSucceeded']?.value){
|
||||||
if (!targets.length) actionContext.addLog({value: '**On save**',});
|
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On save**',});
|
||||||
applyChildren();
|
applyChildren();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import logErrors from './shared/logErrors.js';
|
|||||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||||
|
|
||||||
export default function applyBuff(node, actionContext) {
|
export default function applyBuff(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
@@ -32,7 +33,9 @@ export default function applyBuff(node, actionContext){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
addChildrenToPropList(node.children);
|
addChildrenToPropList(node.children);
|
||||||
|
if (!prop.skipCrystalization) {
|
||||||
crystalizeVariables({ propList, actionContext });
|
crystalizeVariables({ propList, actionContext });
|
||||||
|
}
|
||||||
|
|
||||||
let oldParent = {
|
let oldParent = {
|
||||||
id: prop.parent.id,
|
id: prop.parent.id,
|
||||||
@@ -43,7 +46,7 @@ export default function applyBuff(node, actionContext){
|
|||||||
copyNodeListToTarget(propList, target, oldParent);
|
copyNodeListToTarget(propList, target, oldParent);
|
||||||
|
|
||||||
//Log the buff
|
//Log the buff
|
||||||
if (prop.name || prop.description?.value){
|
if ((prop.name || prop.description?.value) && !prop.silent) {
|
||||||
if (target._id === actionContext.creature._id) {
|
if (target._id === actionContext.creature._id) {
|
||||||
// Targeting self
|
// Targeting self
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
@@ -96,6 +99,7 @@ function crystalizeVariables({propList, actionContext}){
|
|||||||
delete prop._skipCrystalize;
|
delete prop._skipCrystalize;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Iterate through all the calculations and crystalize them
|
||||||
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
||||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||||
const calcObj = get(prop, key);
|
const calcObj = get(prop, key);
|
||||||
@@ -132,5 +136,36 @@ function crystalizeVariables({propList, actionContext}){
|
|||||||
calcObj.hash = cyrb53(calcObj.calculation);
|
calcObj.hash = cyrb53(calcObj.calculation);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// For each key in the schema
|
||||||
|
computedSchemas[prop.type].inlineCalculationFields().forEach(calcKey => {
|
||||||
|
// That ends in .inlineCalculations
|
||||||
|
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||||
|
const inlineCalcObj = get(prop, key);
|
||||||
|
if (!inlineCalcObj) return;
|
||||||
|
|
||||||
|
// If there is no text, skip
|
||||||
|
if (!inlineCalcObj.text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all the existing calculations
|
||||||
|
let index = -1;
|
||||||
|
inlineCalcObj.text = inlineCalcObj.text.replace(INLINE_CALCULATION_REGEX, () => {
|
||||||
|
index += 1;
|
||||||
|
return `{${inlineCalcObj.inlineCalculations[index].calculation}}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the value to the uncomputed string
|
||||||
|
inlineCalcObj.value = inlineCalcObj.text;
|
||||||
|
|
||||||
|
// Write a new hash
|
||||||
|
const inlineCalcHash = cyrb53(inlineCalcObj.text);
|
||||||
|
if (inlineCalcHash === inlineCalcObj.hash) {
|
||||||
|
// Skip if nothing changed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inlineCalcObj.hash = inlineCalcHash;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function applyBuffRemover(node, actionContext) {
|
|||||||
const prop = node.node;
|
const prop = node.node;
|
||||||
|
|
||||||
// Log Name
|
// Log Name
|
||||||
if (prop.name){
|
if (prop.name && !prop.silent){
|
||||||
actionContext.addLog({ name: prop.name });
|
actionContext.addLog({ name: prop.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export default function applyBuffRemover(node, actionContext) {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removeBuff(nearestBuff, actionContext);
|
removeBuff(nearestBuff, actionContext, prop);
|
||||||
} else {
|
} else {
|
||||||
// Get all the buffs targeted by tags
|
// Get all the buffs targeted by tags
|
||||||
const allBuffs = getPropertiesOfType(actionContext.creature._id, 'buff');
|
const allBuffs = getPropertiesOfType(actionContext.creature._id, 'buff');
|
||||||
@@ -41,7 +41,7 @@ export default function applyBuffRemover(node, actionContext) {
|
|||||||
if (prop.removeAll) {
|
if (prop.removeAll) {
|
||||||
// Remove all matching buffs
|
// Remove all matching buffs
|
||||||
targetedBuffs.forEach(buff => {
|
targetedBuffs.forEach(buff => {
|
||||||
removeBuff(buff, actionContext);
|
removeBuff(buff, actionContext, prop);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Sort in reverse order
|
// Sort in reverse order
|
||||||
@@ -49,7 +49,7 @@ export default function applyBuffRemover(node, actionContext) {
|
|||||||
// Remove the one with the highest order
|
// Remove the one with the highest order
|
||||||
const buff = targetedBuffs[0];
|
const buff = targetedBuffs[0];
|
||||||
if (buff) {
|
if (buff) {
|
||||||
removeBuff(buff, actionContext);
|
removeBuff(buff, actionContext, prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,8 @@ export default function applyBuffRemover(node, actionContext) {
|
|||||||
node.children.forEach(child => applyProperty(child, actionContext));
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeBuff(buff, actionContext) {
|
function removeBuff(buff, actionContext, prop) {
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: 'Removed',
|
name: 'Removed',
|
||||||
value: `${buff.name || 'Buff'}`
|
value: `${buff.name || 'Buff'}`
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { some, intersection, difference, remove } from 'lodash';
|
import { some, intersection, difference, remove, includes } from 'lodash';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
||||||
@@ -128,7 +128,7 @@ export default function applyDamage(node, actionContext){
|
|||||||
// There are no targets, just log the result
|
// There are no targets, just log the result
|
||||||
logValue.push(`**${damage}** ${suffix}`);
|
logValue.push(`**${damage}** ${suffix}`);
|
||||||
}
|
}
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: logValue.join('\n'),
|
value: logValue.join('\n'),
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -147,21 +147,21 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
multiplier.immunity &&
|
multiplier.immunity &&
|
||||||
some(multiplier.immunities, multiplierAppliesTo(damageProp))
|
some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Immune to ${damageTypeText}`);
|
logValue.push(`Immune to ${damageTypeText}`);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
multiplier.resistance &&
|
multiplier.resistance &&
|
||||||
some(multiplier.resistances, multiplierAppliesTo(damageProp))
|
some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Resistant to ${damageTypeText}`);
|
logValue.push(`Resistant to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage / 2);
|
damage = Math.floor(damage / 2);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
multiplier.vulnerability &&
|
multiplier.vulnerability &&
|
||||||
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp))
|
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Vulnerable to ${damageTypeText}`);
|
logValue.push(`Vulnerable to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage * 2);
|
damage = Math.floor(damage * 2);
|
||||||
@@ -170,8 +170,11 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
function multiplierAppliesTo(damageProp){
|
function multiplierAppliesTo(damageProp, multiplierType) {
|
||||||
return multiplier => {
|
return multiplier => {
|
||||||
|
// Apply the default 'ignore x' tags
|
||||||
|
if (includes(damageProp.tags, `ignore ${multiplierType}`)) return false;
|
||||||
|
|
||||||
const hasRequiredTags = difference(
|
const hasRequiredTags = difference(
|
||||||
multiplier.includeTags, damageProp.tags
|
multiplier.includeTags, damageProp.tags
|
||||||
).length === 0;
|
).length === 0;
|
||||||
@@ -219,6 +222,16 @@ function dealDamage({target, damageType, amount, actionContext}){
|
|||||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||||
healthBars.forEach(healthBar => {
|
healthBars.forEach(healthBar => {
|
||||||
if (damageLeft === 0) return;
|
if (damageLeft === 0) return;
|
||||||
|
// Replace the healthbar by the one in the action context if we can
|
||||||
|
// The damagePropertyWork function bashes the prop with the damage
|
||||||
|
// So we can use the new value in later action properties
|
||||||
|
if (healthBar.variableName) {
|
||||||
|
const targetHealthBar = target.variables[healthBar.variableName];
|
||||||
|
if (targetHealthBar?._id === healthBar._id) {
|
||||||
|
healthBar = targetHealthBar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do the damage
|
||||||
let damageAdded = damagePropertyWork({
|
let damageAdded = damagePropertyWork({
|
||||||
prop: healthBar,
|
prop: healthBar,
|
||||||
operation: 'increment',
|
operation: 'increment',
|
||||||
@@ -226,6 +239,14 @@ function dealDamage({target, damageType, amount, actionContext}){
|
|||||||
actionContext
|
actionContext
|
||||||
});
|
});
|
||||||
damageLeft -= damageAdded;
|
damageLeft -= damageAdded;
|
||||||
|
// Prevent overflow
|
||||||
|
if (
|
||||||
|
damageType === 'healing' ?
|
||||||
|
healthBar.healthBarNoHealingOverflow :
|
||||||
|
healthBar.healthBarNoDamageOverflow
|
||||||
|
) {
|
||||||
|
damageLeft = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return totalDamage;
|
return totalDamage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
|
export default function applyFolder(node, actionContext) {
|
||||||
|
// Apply triggers
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
// Apply children
|
||||||
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ export default function applySavingThrow(node, actionContext){
|
|||||||
});
|
});
|
||||||
return node.children.forEach(child => applyProperty(child, actionContext));
|
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
}
|
}
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
value: `DC **${dc}**`,
|
value: `DC **${dc}**`,
|
||||||
inline: true,
|
inline: true,
|
||||||
@@ -94,7 +94,7 @@ export default function applySavingThrow(node, actionContext){
|
|||||||
} else {
|
} else {
|
||||||
scope['$saveFailed'] = {value: true};
|
scope['$saveFailed'] = {value: true};
|
||||||
}
|
}
|
||||||
actionContext.addLog({
|
if (!prop.silent) actionContext.addLog({
|
||||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||||
value: resultPrefix + '\n**' + result + '**',
|
value: resultPrefix + '\n**' + result + '**',
|
||||||
inline: true,
|
inline: true,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import logErrors from './logErrors.js';
|
|||||||
export default function recalculateCalculation(calc, actionContext, context){
|
export default function recalculateCalculation(calc, actionContext, context){
|
||||||
if (!calc?.parseNode) return;
|
if (!calc?.parseNode) return;
|
||||||
calc._parseLevel = 'reduce';
|
calc._parseLevel = 'reduce';
|
||||||
applyEffectsToCalculationParseNode(calc, actionContext.log);
|
applyEffectsToCalculationParseNode(calc, actionContext);
|
||||||
evaluateCalculation(calc, actionContext.scope, context);
|
evaluateCalculation(calc, actionContext.scope, context);
|
||||||
logErrors(calc.errors, actionContext.log);
|
logErrors(calc.errors, actionContext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function applyTrigger(trigger, prop, actionContext) {
|
|||||||
recalculateInlineCalculations(trigger.description, actionContext);
|
recalculateInlineCalculations(trigger.description, actionContext);
|
||||||
content.value = trigger.description.value;
|
content.value = trigger.description.value;
|
||||||
}
|
}
|
||||||
actionContext.addLog(content);
|
if(!trigger.silent) actionContext.addLog(content);
|
||||||
|
|
||||||
// Get all the trigger's properties and apply them
|
// Get all the trigger's properties and apply them
|
||||||
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
||||||
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
|
||||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const doAction = new ValidatedMethod({
|
||||||
@@ -21,6 +20,10 @@ const doAction = new ValidatedMethod({
|
|||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
ritual: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
targetIds: {
|
targetIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
@@ -42,7 +45,7 @@ const doAction = new ValidatedMethod({
|
|||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ spellId, slotId, targetIds = [], scope = {} }) {
|
run({ spellId, slotId, ritual, targetIds = [], scope = {} }) {
|
||||||
// Get action context
|
// Get action context
|
||||||
let spell = CreatureProperties.findOne(spellId);
|
let spell = CreatureProperties.findOne(spellId);
|
||||||
const creatureId = spell.ancestors[0].id;
|
const creatureId = spell.ancestors[0].id;
|
||||||
@@ -65,9 +68,8 @@ const doAction = new ValidatedMethod({
|
|||||||
let slotLevel = spell.level || 0;
|
let slotLevel = spell.level || 0;
|
||||||
let slot;
|
let slot;
|
||||||
|
|
||||||
actionContext.scope['slotLevel'] = slotLevel;
|
// If a spell requires a slot, make sure a slot is spent
|
||||||
|
if (spell.level && !spell.castWithoutSpellSlots && !(ritual && spell.ritual)) {
|
||||||
if (slotId && !spell.castWithoutSpellSlots){
|
|
||||||
slot = CreatureProperties.findOne(slotId);
|
slot = CreatureProperties.findOne(slotId);
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
throw new Meteor.Error('No slot',
|
throw new Meteor.Error('No slot',
|
||||||
@@ -104,10 +106,18 @@ const doAction = new ValidatedMethod({
|
|||||||
name: `Casting using a level ${slotLevel} spell slot`
|
name: `Casting using a level ${slotLevel} spell slot`
|
||||||
});
|
});
|
||||||
} else if (slotLevel) {
|
} else if (slotLevel) {
|
||||||
|
if (ritual) {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: `Ritual casting at level ${slotLevel}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: `Casting at level ${slotLevel}`
|
name: `Casting at level ${slotLevel}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionContext.scope['slotLevel'] = slotLevel;
|
||||||
|
|
||||||
// Do the action
|
// Do the action
|
||||||
doActionWork({
|
doActionWork({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import rollDice from '/imports/parser/rollDice.js';
|
|||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
import evaluateCalculation from '/imports/api/engine/computation/utility/evaluateCalculation.js';
|
||||||
|
|
||||||
const doCheck = new ValidatedMethod({
|
const doCheck = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doCheck',
|
name: 'creatureProperties.doCheck',
|
||||||
@@ -72,7 +73,11 @@ function rollCheck(prop, actionContext) {
|
|||||||
throw (`${prop.type} not supported for checks`);
|
throw (`${prop.type} not supported for checks`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rollModifierText = numberToSignedString(rollModifier, true);
|
let rollModifierText = numberToSignedString(rollModifier, true);
|
||||||
|
|
||||||
|
const { effectBonus, effectString } = applyUnresolvedEffects(prop, scope)
|
||||||
|
rollModifierText += effectString;
|
||||||
|
rollModifier += effectBonus;
|
||||||
|
|
||||||
let value, values, resultPrefix;
|
let value, values, resultPrefix;
|
||||||
if (scope['$checkAdvantage'] === 1) {
|
if (scope['$checkAdvantage'] === 1) {
|
||||||
@@ -101,8 +106,29 @@ function rollCheck(prop, actionContext) {
|
|||||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||||
}
|
}
|
||||||
const result = (value + rollModifier) || 0;
|
const result = (value + rollModifier) || 0;
|
||||||
|
scope['$checkDiceRoll'] = value;
|
||||||
|
scope['$checkRoll'] = result;
|
||||||
|
scope['$checkModifier'] = rollModifier;
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: `${resultPrefix} **${result}**`,
|
value: `${resultPrefix} **${result}**`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyUnresolvedEffects(prop, scope) {
|
||||||
|
let effectBonus = 0;
|
||||||
|
let effectString = '';
|
||||||
|
if (!prop.effects) {
|
||||||
|
return { effectBonus, effectString };
|
||||||
|
}
|
||||||
|
prop.effects.forEach(effect => {
|
||||||
|
if (!effect.amount?.parseNode) return;
|
||||||
|
if (effect.operation !== 'add') return;
|
||||||
|
effect.amount._parseLevel = 'reduce';
|
||||||
|
evaluateCalculation(effect.amount, scope);
|
||||||
|
if (typeof effect.amount?.value !== 'number') return;
|
||||||
|
effectBonus += effect.amount.value;
|
||||||
|
effectString += ` ${effect.amount.value < 0 ? '-' : '+'} [${effect.amount.calculation}] ${Math.abs(effect.amount.value)}`
|
||||||
|
});
|
||||||
|
return { effectBonus, effectString };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { EJSON } from 'meteor/ejson';
|
import { EJSON } from 'meteor/ejson';
|
||||||
import createGraph from 'ngraph.graph';
|
import createGraph, { Graph } from 'ngraph.graph';
|
||||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||||
|
|
||||||
|
interface CreatureProperty {
|
||||||
|
_id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class CreatureComputation {
|
export default class CreatureComputation {
|
||||||
constructor(properties, creature, variables){
|
originalPropsById: object;
|
||||||
|
propsById: object;
|
||||||
|
propsWithTag: object;
|
||||||
|
scope: object;
|
||||||
|
props: Array<CreatureProperty>;
|
||||||
|
dependencyGraph: Graph;
|
||||||
|
errors: Array<object>;
|
||||||
|
creature: object;
|
||||||
|
variables: object;
|
||||||
|
|
||||||
|
constructor(properties: Array<CreatureProperty>, creature: object, variables: object) {
|
||||||
// Set up fields
|
// Set up fields
|
||||||
this.originalPropsById = {};
|
this.originalPropsById = {};
|
||||||
this.propsById = {};
|
this.propsById = {};
|
||||||
@@ -29,8 +29,8 @@ function childrenActive(prop){
|
|||||||
// Children of disabled properties are always inactive
|
// Children of disabled properties are always inactive
|
||||||
if (prop.disabled) return false;
|
if (prop.disabled) return false;
|
||||||
switch (prop.type){
|
switch (prop.type){
|
||||||
// Only equipped items have active children
|
// Only equipped items with non-zero quantity have active children
|
||||||
case 'item': return !!prop.equipped;
|
case 'item': return !!prop.equipped && prop.quantity !== 0;
|
||||||
// The children of actions, spells, and triggers are always inactive
|
// The children of actions, spells, and triggers are always inactive
|
||||||
case 'action': return false;
|
case 'action': return false;
|
||||||
case 'spell': return false;
|
case 'spell': return false;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ export default function computeToggleDependencies(node, dependencyGraph){
|
|||||||
prop.enabled
|
prop.enabled
|
||||||
) return;
|
) return;
|
||||||
walkDown(node.children, child => {
|
walkDown(node.children, child => {
|
||||||
// Only for children that aren't inactive
|
|
||||||
if (child.node.inactive) return;
|
|
||||||
// The child nodes depend on the toggle condition compuation
|
// The child nodes depend on the toggle condition compuation
|
||||||
child.node._computationDetails.toggleAncestors.push(prop);
|
child.node._computationDetails.toggleAncestors.push(prop);
|
||||||
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const linkDependenciesByType = {
|
|||||||
effect: linkEffects,
|
effect: linkEffects,
|
||||||
proficiency: linkProficiencies,
|
proficiency: linkProficiencies,
|
||||||
roll: linkRoll,
|
roll: linkRoll,
|
||||||
|
pointBuy: linkPointBuy,
|
||||||
propertySlot: linkSlot,
|
propertySlot: linkSlot,
|
||||||
skill: linkSkill,
|
skill: linkSkill,
|
||||||
spell: linkAction,
|
spell: linkAction,
|
||||||
@@ -242,6 +243,28 @@ function linkDamageMultiplier(dependencyGraph, prop) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkPointBuy(dependencyGraph, prop){
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'min' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'max' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'cost' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'total' });
|
||||||
|
prop.values?.forEach(row => {
|
||||||
|
// Wrap the document in a new object so we don't bash it unintentionally
|
||||||
|
const pointBuyRow = {
|
||||||
|
...row,
|
||||||
|
type: 'pointBuyRow',
|
||||||
|
tableName: prop.name,
|
||||||
|
tableId: prop._id,
|
||||||
|
}
|
||||||
|
dependencyGraph.addNode(row._id, pointBuyRow);
|
||||||
|
linkVariableName(dependencyGraph, pointBuyRow);
|
||||||
|
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.min' });
|
||||||
|
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.max' });
|
||||||
|
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.cost' });
|
||||||
|
});
|
||||||
|
if (prop.inactive) return;
|
||||||
|
}
|
||||||
|
|
||||||
function linkProficiencies(dependencyGraph, prop){
|
function linkProficiencies(dependencyGraph, prop){
|
||||||
// The stats depend on the proficiency
|
// The stats depend on the proficiency
|
||||||
if (prop.inactive) return;
|
if (prop.inactive) return;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import computeToggleDependencies from './buildComputation/computeToggleDependenc
|
|||||||
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
||||||
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
||||||
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
||||||
import CreatureComputation from './CreatureComputation.js';
|
import CreatureComputation from './CreatureComputation.ts';
|
||||||
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import _variable from './computeByType/computeVariable.js';
|
|||||||
import action from './computeByType/computeAction.js';
|
import action from './computeByType/computeAction.js';
|
||||||
import attribute from './computeByType/computeAttribute.js';
|
import attribute from './computeByType/computeAttribute.js';
|
||||||
import skill from './computeByType/computeSkill.js';
|
import skill from './computeByType/computeSkill.js';
|
||||||
|
import pointBuy from './computeByType/computePointBuy.js';
|
||||||
import propertySlot from './computeByType/computeSlot.js';
|
import propertySlot from './computeByType/computeSlot.js';
|
||||||
import container from './computeByType/computeContainer.js';
|
import container from './computeByType/computeContainer.js';
|
||||||
import _calculation from './computeByType/computeCalculation.js';
|
import _calculation from './computeByType/computeCalculation.js';
|
||||||
@@ -13,6 +14,7 @@ export default Object.freeze({
|
|||||||
attribute,
|
attribute,
|
||||||
container,
|
container,
|
||||||
skill,
|
skill,
|
||||||
|
pointBuy,
|
||||||
propertySlot,
|
propertySlot,
|
||||||
spell: action,
|
spell: action,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { has } from 'lodash';
|
||||||
|
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
||||||
|
|
||||||
|
export default function computePointBuy(computation, node) {
|
||||||
|
const prop = node.data;
|
||||||
|
const tableMin = prop.min?.value || null;
|
||||||
|
const tableMax = prop.max?.value || null;
|
||||||
|
prop.spent = 0;
|
||||||
|
prop.values?.forEach(row => {
|
||||||
|
// Clean up added properties
|
||||||
|
// delete row.tableId;
|
||||||
|
// delete row.tableName;
|
||||||
|
// delete row.type;
|
||||||
|
|
||||||
|
row.spent = 0;
|
||||||
|
if (row.value === undefined) return;
|
||||||
|
const min = has(row, 'min.value') ? row.min.value : tableMin;
|
||||||
|
const max = has(row, 'max.value') ? row.max.value : tableMax;
|
||||||
|
const costFunction = EJSON.clone(row.cost || prop.cost);
|
||||||
|
if (costFunction) costFunction.parseLevel = 'reduce';
|
||||||
|
|
||||||
|
// Check min and max
|
||||||
|
if (min !== null && row.value < min) {
|
||||||
|
row.value = min;
|
||||||
|
}
|
||||||
|
if (max !== null && row.value > max) {
|
||||||
|
row.value = max;
|
||||||
|
}
|
||||||
|
// Evaluate the cost function
|
||||||
|
if (!costFunction) return;
|
||||||
|
evaluateCalculation(costFunction, { ...computation.scope, value: row.value });
|
||||||
|
// Write calculation errors
|
||||||
|
costFunction.errors?.forEach(error => {
|
||||||
|
if (error?.message) {
|
||||||
|
row.errors = row.errors || [];
|
||||||
|
error.message = 'Cost calculation error.\n' + error.message;
|
||||||
|
row.errors.push(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Number.isFinite(costFunction.value)) {
|
||||||
|
row.spent = costFunction.value;
|
||||||
|
prop.spent += costFunction.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
prop.pointsLeft = (prop.total?.value || 0) - (prop.spent || 0);
|
||||||
|
if (prop.spent > prop.total?.value) {
|
||||||
|
prop.errors = prop.errors || [];
|
||||||
|
prop.errors.push({
|
||||||
|
type: 'pointBuyError',
|
||||||
|
message: 'Spent more than total points available',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// by computeVariableAsSkill
|
// by computeVariableAsSkill
|
||||||
export default function computeSkill(computation, node){
|
export default function computeSkill(computation, node){
|
||||||
const prop = node.data;
|
const prop = node.data;
|
||||||
prop.proficiency = prop.baseProficiency;
|
prop.proficiency = prop.baseProficiency || 0;
|
||||||
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
||||||
// Multiply the proficiency bonus by the actual proficiency
|
// Multiply the proficiency bonus by the actual proficiency
|
||||||
if(prop.proficiency === 0.49){
|
if(prop.proficiency === 0.49){
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
|||||||
// get current defining prop
|
// get current defining prop
|
||||||
const definingProp = node.data.definingProp;
|
const definingProp = node.data.definingProp;
|
||||||
// Find the last defining prop
|
// Find the last defining prop
|
||||||
if (!definingProp || prop.order > definingProp.order){
|
if (
|
||||||
|
!definingProp ||
|
||||||
|
prop.type !== 'pointBuyRow' && (
|
||||||
|
definingProp.type === 'pointBuyRow' ||
|
||||||
|
prop.order > definingProp.order
|
||||||
|
)
|
||||||
|
) {
|
||||||
// override the current defining prop
|
// override the current defining prop
|
||||||
overrideProp(definingProp, node);
|
overrideProp(definingProp, node);
|
||||||
// set this prop as the new defining prop
|
// set this prop as the new defining prop
|
||||||
@@ -18,9 +24,32 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aggregate the base value due to the defining properties
|
// Aggregate the base value due to the defining properties
|
||||||
const propBaseValue = prop.baseValue?.value;
|
let propBaseValue = prop.baseValue?.value;
|
||||||
|
// Point buy rows use prop.value instead of prop.baseValue
|
||||||
|
if (prop.type === 'pointBuyRow') {
|
||||||
|
propBaseValue = prop.value;
|
||||||
|
}
|
||||||
|
|
||||||
if (propBaseValue === undefined) return;
|
if (propBaseValue === undefined) return;
|
||||||
|
// Store a summary of the definition as a base value effect
|
||||||
|
node.data.effects = node.data.effects || [];
|
||||||
|
if (prop.type === 'pointBuyRow') {
|
||||||
|
node.data.effects.push({
|
||||||
|
_id: prop.tableId,
|
||||||
|
name: prop.tableName,
|
||||||
|
operation: 'base',
|
||||||
|
amount: { value: propBaseValue },
|
||||||
|
type: 'pointBuy',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
node.data.effects.push({
|
||||||
|
_id: prop._id,
|
||||||
|
name: prop.name,
|
||||||
|
operation: 'base',
|
||||||
|
amount: { value: propBaseValue },
|
||||||
|
type: prop.type,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
||||||
node.data.baseValue = propBaseValue;
|
node.data.baseValue = propBaseValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
export default function aggregateEffect({node, linkedNode, link}){
|
export default function aggregateEffect({node, linkedNode, link}){
|
||||||
if (link.data !== 'effect') return;
|
if (link.data !== 'effect') return;
|
||||||
// store the effect aggregator, its presence indicates that the variable is
|
// store the effect aggregator, its presence indicates that the variable is
|
||||||
@@ -19,11 +21,23 @@ export default function aggregateEffect({node, linkedNode, link}){
|
|||||||
|
|
||||||
// Store a summary of the effect itself
|
// Store a summary of the effect itself
|
||||||
node.data.effects = node.data.effects || [];
|
node.data.effects = node.data.effects || [];
|
||||||
|
// Store either just
|
||||||
|
let effectAmount;
|
||||||
|
if (!linkedNode.data.amount) {
|
||||||
|
effectAmount = undefined;
|
||||||
|
} else if (typeof linkedNode.data.amount.value === 'string') {
|
||||||
|
effectAmount = pick(linkedNode.data.amount, [
|
||||||
|
'calculation', 'parseNode', 'parseError', 'value'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
effectAmount = pick(linkedNode.data.amount, ['value']);
|
||||||
|
}
|
||||||
node.data.effects.push({
|
node.data.effects.push({
|
||||||
_id: linkedNode.data._id,
|
_id: linkedNode.data._id,
|
||||||
name: linkedNode.data.name,
|
name: linkedNode.data.name,
|
||||||
operation: linkedNode.data.operation,
|
operation: linkedNode.data.operation,
|
||||||
amount: linkedNode.data.amount && {value: linkedNode.data.amount.value},
|
amount: effectAmount,
|
||||||
|
type: linkedNode.data.type,
|
||||||
// ancestors: linkedNode.data.ancestors,
|
// ancestors: linkedNode.data.ancestors,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,7 +46,7 @@ export default function aggregateEffect({node, linkedNode, link}){
|
|||||||
// Get the result of the effect
|
// Get the result of the effect
|
||||||
const result = linkedNode.data.amount?.value;
|
const result = linkedNode.data.amount?.value;
|
||||||
// Skip aggregating if the result is not resolved completely
|
// Skip aggregating if the result is not resolved completely
|
||||||
if (typeof result === 'string') return;
|
if (typeof result === 'string' || result === undefined) return;
|
||||||
// Aggregate the effect based on its operation
|
// Aggregate the effect based on its operation
|
||||||
switch(linkedNode.data.operation){
|
switch(linkedNode.data.operation){
|
||||||
case 'base':
|
case 'base':
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export default function computeVariableAsSkill(computation, node, prop){
|
|||||||
const aggregator = node.data.effectAggregator;
|
const aggregator = node.data.effectAggregator;
|
||||||
const aggregatorBase = aggregator?.base || 0;
|
const aggregatorBase = aggregator?.base || 0;
|
||||||
|
|
||||||
|
// Store effects
|
||||||
|
prop.effects = node.data.effects;
|
||||||
|
|
||||||
// If there is no aggregator, determine if the prop can hide, then exit
|
// If there is no aggregator, determine if the prop can hide, then exit
|
||||||
if (!aggregator){
|
if (!aggregator){
|
||||||
prop.hide = statBase === undefined &&
|
prop.hide = statBase === undefined &&
|
||||||
@@ -71,8 +74,6 @@ export default function computeVariableAsSkill(computation, node, prop){
|
|||||||
prop.fail = aggregator.fail;
|
prop.fail = aggregator.fail;
|
||||||
// Rollbonus
|
// Rollbonus
|
||||||
prop.rollBonuses = aggregator.rollBonus;
|
prop.rollBonuses = aggregator.rollBonus;
|
||||||
// Store effects
|
|
||||||
prop.effects = node.data.effects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function aggregateAbilityEffects({computation, skillNode, abilityNode}){
|
function aggregateAbilityEffects({computation, skillNode, abilityNode}){
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default function evaluateToggles(computation, node){
|
|||||||
let toggles = prop._computationDetails?.toggleAncestors;
|
let toggles = prop._computationDetails?.toggleAncestors;
|
||||||
if (!toggles) return;
|
if (!toggles) return;
|
||||||
toggles.forEach(toggle => {
|
toggles.forEach(toggle => {
|
||||||
if (prop.inactive || !toggle.condition) return;
|
if (!toggle.condition) return;
|
||||||
if (!toggle.condition.value){
|
if (!toggle.condition.value){
|
||||||
prop.inactive = true;
|
prop.inactive = true;
|
||||||
prop.deactivatedByToggle = true;
|
prop.deactivatedByToggle = true;
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ export default function getEffectivePropTags(prop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tags for some string properties
|
// Tags for some string properties
|
||||||
|
if (prop.variableName) tags.push(prop.variableName);
|
||||||
if (prop.damageType) tags.push(prop.damageType);
|
if (prop.damageType) tags.push(prop.damageType);
|
||||||
if (prop.skillType) tags.push(prop.skillType);
|
if (prop.skillType) tags.push(prop.skillType);
|
||||||
|
if (prop.actionType) tags.push(prop.actionType);
|
||||||
if (prop.attributeType) tags.push(prop.attributeType);
|
if (prop.attributeType) tags.push(prop.attributeType);
|
||||||
if (prop.reset) tags.push(prop.reset);
|
if (prop.reset) tags.push(prop.reset);
|
||||||
return tags;
|
return tags;
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ export function getCreature(creatureId) {
|
|||||||
if (loadedCreatures.has(creatureId)) {
|
if (loadedCreatures.has(creatureId)) {
|
||||||
const loadedCreature = loadedCreatures.get(creatureId);
|
const loadedCreature = loadedCreatures.get(creatureId);
|
||||||
const creature = loadedCreature.creature;
|
const creature = loadedCreature.creature;
|
||||||
if (creature) return creature;
|
if (creature) {
|
||||||
|
const cloneCreature = EJSON.clone(creature);
|
||||||
|
return cloneCreature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// console.time(`Cache miss on Creature: ${creatureId}`);
|
// console.time(`Cache miss on Creature: ${creatureId}`);
|
||||||
const creature = Creatures.findOne(creatureId);
|
const creature = Creatures.findOne(creatureId);
|
||||||
@@ -109,7 +112,10 @@ export function getVariables(creatureId) {
|
|||||||
if (loadedCreatures.has(creatureId)) {
|
if (loadedCreatures.has(creatureId)) {
|
||||||
const loadedCreature = loadedCreatures.get(creatureId);
|
const loadedCreature = loadedCreatures.get(creatureId);
|
||||||
const variables = loadedCreature.variables;
|
const variables = loadedCreature.variables;
|
||||||
if (variables) return variables;
|
if (variables) {
|
||||||
|
const cloneVarables = EJSON.clone(variables);
|
||||||
|
return cloneVarables;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// console.time(`Cache miss on variables: ${creatureId}`);
|
// console.time(`Cache miss on variables: ${creatureId}`);
|
||||||
const variables = CreatureVariables.findOne({_creatureId: creatureId});
|
const variables = CreatureVariables.findOne({_creatureId: creatureId});
|
||||||
|
|||||||
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||||
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
|
import {
|
||||||
|
assertDocCopyPermission,
|
||||||
|
assertDocEditPermission
|
||||||
|
} from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import {
|
||||||
|
setLineageOfDocs,
|
||||||
|
renewDocIds
|
||||||
|
} from '/imports/api/parenting/parenting.js';
|
||||||
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
|
var snackbar;
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar = require(
|
||||||
|
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
||||||
|
).snackbar
|
||||||
|
}
|
||||||
|
|
||||||
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
|
const copyLibraryNodeTo = new ValidatedMethod({
|
||||||
|
name: 'libraryNodes.copyTo',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: RefSchema,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 1,
|
||||||
|
timeInterval: 10000,
|
||||||
|
},
|
||||||
|
run({ _id, parent }) {
|
||||||
|
if (parent.collection !== 'libraryNodes' && parent.collection !== 'libraries') {
|
||||||
|
throw new Meteor.Error('Invalid destination',
|
||||||
|
'Library documents can only be copied to destinations inside other libraries'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const libraryNode = LibraryNodes.findOne(_id);
|
||||||
|
const parentDoc = fetchDocByRef(parent);
|
||||||
|
assertDocCopyPermission(libraryNode, this.userId);
|
||||||
|
assertDocEditPermission(parentDoc, this.userId);
|
||||||
|
|
||||||
|
let decendants = LibraryNodes.find({
|
||||||
|
'ancestors.id': _id,
|
||||||
|
removed: { $ne: true },
|
||||||
|
}, {
|
||||||
|
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||||
|
sort: { order: 1 },
|
||||||
|
}).fetch();
|
||||||
|
|
||||||
|
if (decendants.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||||
|
decendants.pop();
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar({
|
||||||
|
text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = [libraryNode, ...decendants];
|
||||||
|
|
||||||
|
const newAncestry = parentDoc.ancestors || [];
|
||||||
|
newAncestry.push(parent);
|
||||||
|
// re-map all the ancestors
|
||||||
|
setLineageOfDocs({
|
||||||
|
docArray: nodes,
|
||||||
|
newAncestry,
|
||||||
|
oldParent: libraryNode.parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give the docs new IDs without breaking internal references
|
||||||
|
renewDocIds({ docArray: nodes });
|
||||||
|
|
||||||
|
// Order the root node
|
||||||
|
libraryNode.order = (parentDoc.order || 0) + 0.5;
|
||||||
|
|
||||||
|
LibraryNodes.batchInsert(nodes);
|
||||||
|
|
||||||
|
// Tree structure changed by inserts, reorder the tree
|
||||||
|
reorderDocs({
|
||||||
|
collection: LibraryNodes,
|
||||||
|
ancestorId: parent.collection === 'libraries' ? parent.id : parentDoc.ancestors[0].id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default copyLibraryNodeTo;
|
||||||
@@ -16,7 +16,7 @@ if (Meteor.isClient){
|
|||||||
).snackbar
|
).snackbar
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUPLICATE_CHILDREN_LIMIT = 50;
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
const duplicateLibraryNode = new ValidatedMethod({
|
const duplicateLibraryNode = new ValidatedMethod({
|
||||||
name: 'libraryNodes.duplicate',
|
name: 'libraryNodes.duplicate',
|
||||||
@@ -28,7 +28,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 1,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id }) {
|
run({ _id }) {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
import '/imports/api/library/methods/copyLibraryNodeTo.js';
|
||||||
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
||||||
import '/imports/api/library/methods/updateReferenceNode.js';
|
import '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
let SoftRemovableSchema = new SimpleSchema({
|
let SoftRemovableSchema = new SimpleSchema({
|
||||||
"removed": {
|
'removed': {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
"removedAt": {
|
'removedAt': {
|
||||||
type: Date,
|
type: Date,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
"removedWith": {
|
'removedWith': {
|
||||||
optional: true,
|
optional: true,
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ let ActionSchema = createPropertySchema({
|
|||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyActionSchema = createPropertySchema({
|
const ComputedOnlyActionSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ const AdjustmentSchema = createPropertySchema({
|
|||||||
allowedValues: ['set', 'increment'],
|
allowedValues: ['set', 'increment'],
|
||||||
defaultValue: 'increment',
|
defaultValue: 'increment',
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyAdjustmentSchema = createPropertySchema({
|
const ComputedOnlyAdjustmentSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Control how the health bar handles overflow
|
||||||
|
healthBarNoDamageOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarNoHealingOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Control when the health bar takes damage or healing
|
||||||
healthBarDamageOrder: {
|
healthBarDamageOrder: {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -107,6 +117,14 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
hideWhenTotalZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
hideWhenValueZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// Automatically zero the adjustment on these conditions
|
// Automatically zero the adjustment on these conditions
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -176,6 +194,7 @@ let ComputedOnlyAttributeSchema = createPropertySchema({
|
|||||||
effects: {
|
effects: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
'effects.$': {
|
'effects.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ let BranchSchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
parseLevel: 'compile',
|
parseLevel: 'compile',
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlyBranchSchema = createPropertySchema({
|
let ComputedOnlyBranchSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ let BuffRemoverSchema = createPropertySchema({
|
|||||||
type: String,
|
type: String,
|
||||||
max: STORAGE_LIMITS.tagLength,
|
max: STORAGE_LIMITS.tagLength,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlyBuffRemoverSchema = createPropertySchema({});
|
let ComputedOnlyBuffRemoverSchema = createPropertySchema({});
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ let BuffSchema = createPropertySchema({
|
|||||||
],
|
],
|
||||||
defaultValue: 'target',
|
defaultValue: 'target',
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Prevent the children from being crystalized
|
||||||
|
skipCrystalization: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlyBuffSchema = createPropertySchema({
|
let ComputedOnlyBuffSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ const DamageSchema = createPropertySchema({
|
|||||||
defaultValue: 'slashing',
|
defaultValue: 'slashing',
|
||||||
regEx: VARIABLE_NAME_REGEX,
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyDamageSchema = createPropertySchema({
|
const ComputedOnlyDamageSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
|
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PointBuys are reason-value attached to skills and abilities
|
* PointBuys are reason-value attached to skills and abilities
|
||||||
@@ -13,13 +14,6 @@ let PointBuySchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
},
|
},
|
||||||
variableName: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
regEx: VARIABLE_NAME_REGEX,
|
|
||||||
min: 2,
|
|
||||||
max: STORAGE_LIMITS.variableName,
|
|
||||||
},
|
|
||||||
ignored: {
|
ignored: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -27,10 +21,18 @@ let PointBuySchema = createPropertySchema({
|
|||||||
'values': {
|
'values': {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
|
maxCount: STORAGE_LIMITS.pointBuyRowsCount,
|
||||||
},
|
},
|
||||||
'values.$': {
|
'values.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
'values.$._id': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
autoValue(){
|
||||||
|
if (!this.isSet) return Random.id();
|
||||||
|
}
|
||||||
|
},
|
||||||
'values.$.name': {
|
'values.$.name': {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -47,6 +49,18 @@ let PointBuySchema = createPropertySchema({
|
|||||||
type: Number,
|
type: Number,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
'values.$.min': {
|
||||||
|
type: 'fieldToCompute',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'values.$.max': {
|
||||||
|
type: 'fieldToCompute',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'values.$.cost': {
|
||||||
|
type: 'fieldToCompute',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
min: {
|
min: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -62,6 +76,7 @@ let PointBuySchema = createPropertySchema({
|
|||||||
cost: {
|
cost: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,11 +89,46 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
total: {
|
cost: {
|
||||||
|
type: 'computedOnlyField',
|
||||||
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
|
},
|
||||||
|
'values': {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
maxCount: STORAGE_LIMITS.pointBuyRowsCount,
|
||||||
|
},
|
||||||
|
'values.$': {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
'values.$.min': {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
cost: {
|
'values.$.max': {
|
||||||
|
type: 'computedOnlyField',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'values.$.cost': {
|
||||||
|
type: 'computedOnlyField',
|
||||||
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
|
},
|
||||||
|
'values.$.spent': {
|
||||||
|
type: Number,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'values.$.errors': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'values.$.errors.$': {
|
||||||
|
type: ErrorSchema,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
@@ -87,6 +137,19 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
|
pointsLeft: {
|
||||||
|
type: Number,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'errors.$': {
|
||||||
|
type: ErrorSchema,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedPointBuySchema = new SimpleSchema()
|
const ComputedPointBuySchema = new SimpleSchema()
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ let SavingThrowSchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.variableName,
|
max: STORAGE_LIMITS.variableName,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlySavingThrowSchema = createPropertySchema({
|
const ComputedOnlySavingThrowSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ let ComputedOnlySkillSchema = createPropertySchema({
|
|||||||
effects: {
|
effects: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
'effects.$': {
|
'effects.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -109,6 +109,11 @@ let TriggerSchema = createPropertySchema({
|
|||||||
type: String,
|
type: String,
|
||||||
max: STORAGE_LIMITS.tagLength,
|
max: STORAGE_LIMITS.tagLength,
|
||||||
},
|
},
|
||||||
|
// Prevent the property from showing up in the log
|
||||||
|
silent: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedOnlyTriggerSchema = createPropertySchema({
|
const ComputedOnlyTriggerSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
|||||||
import { ComputedOnlyFolderSchema } from '/imports/api/properties/Folders.js';
|
import { ComputedOnlyFolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
|
import { ComputedOnlyPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||||
import { ComputedOnlyProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ComputedOnlyProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ComputedOnlyReferenceSchema } from '/imports/api/properties/References.js';
|
import { ComputedOnlyReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
@@ -46,6 +47,7 @@ const propertySchemasIndex = {
|
|||||||
folder: ComputedOnlyFolderSchema,
|
folder: ComputedOnlyFolderSchema,
|
||||||
item: ComputedOnlyItemSchema,
|
item: ComputedOnlyItemSchema,
|
||||||
note: ComputedOnlyNoteSchema,
|
note: ComputedOnlyNoteSchema,
|
||||||
|
pointBuy: ComputedOnlyPointBuySchema,
|
||||||
proficiency: ComputedOnlyProficiencySchema,
|
proficiency: ComputedOnlyProficiencySchema,
|
||||||
propertySlot: ComputedOnlySlotSchema,
|
propertySlot: ComputedOnlySlotSchema,
|
||||||
reference: ComputedOnlyReferenceSchema,
|
reference: ComputedOnlyReferenceSchema,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { ComputedFeatureSchema } from '/imports/api/properties/Features.js';
|
|||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
|
import { ComputedPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
@@ -44,6 +45,7 @@ const propertySchemasIndex = {
|
|||||||
feature: ComputedFeatureSchema,
|
feature: ComputedFeatureSchema,
|
||||||
folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
note: ComputedNoteSchema,
|
note: ComputedNoteSchema,
|
||||||
|
pointBuy: ComputedPointBuySchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: ComputedSlotSchema,
|
propertySlot: ComputedSlotSchema,
|
||||||
reference: ReferenceSchema,
|
reference: ReferenceSchema,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { EffectSchema } from '/imports/api/properties/Effects.js';
|
|||||||
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
|
import { PointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
@@ -44,6 +45,7 @@ const propertySchemasIndex = {
|
|||||||
feature: FeatureSchema,
|
feature: FeatureSchema,
|
||||||
folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
note: NoteSchema,
|
note: NoteSchema,
|
||||||
|
pointBuy: PointBuySchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: SlotSchema,
|
propertySlot: SlotSchema,
|
||||||
reference: ReferenceSchema,
|
reference: ReferenceSchema,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ let SharingSchema = new SimpleSchema({
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
|
readersCanCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SharingSchema;
|
export default SharingSchema;
|
||||||
|
|||||||
@@ -27,6 +27,26 @@ const setPublic = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setReadersCanCopy = new ValidatedMethod({
|
||||||
|
name: 'sharing.setReadersCanCopy',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
docRef: RefSchema,
|
||||||
|
readersCanCopy: { type: Boolean },
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ docRef, readersCanCopy }) {
|
||||||
|
let doc = fetchDocByRef(docRef);
|
||||||
|
assertOwnership(doc, this.userId);
|
||||||
|
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||||
|
$set: { readersCanCopy },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const updateUserSharePermissions = new ValidatedMethod({
|
const updateUserSharePermissions = new ValidatedMethod({
|
||||||
name: 'sharing.updateUserSharePermissions',
|
name: 'sharing.updateUserSharePermissions',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
@@ -129,4 +149,4 @@ const transferOwnership = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
export { setPublic, setReadersCanCopy, updateUserSharePermissions, transferOwnership };
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function assertdocExists(doc){
|
|||||||
export function assertOwnership(doc, userId) {
|
export function assertOwnership(doc, userId) {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
|
|
||||||
if (doc.owner === userId) {
|
if (doc.owner === userId) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -37,7 +38,6 @@ export function assertEditPermission(doc, userId) {
|
|||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'services.patreon': 1,
|
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -59,6 +59,43 @@ export function assertEditPermission(doc, userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can edit the root document which manages its own sharing
|
||||||
|
* permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertCopyPermission(doc, userId) {
|
||||||
|
assertIdValid(userId);
|
||||||
|
assertdocExists(doc);
|
||||||
|
const user = Meteor.users.findOne(userId, {
|
||||||
|
fields: {
|
||||||
|
'roles': 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin override
|
||||||
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the user is authorized for this specific document
|
||||||
|
if (
|
||||||
|
doc.owner === userId ||
|
||||||
|
_.contains(doc.writers, userId)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
(_.contains(doc.readers, userId) || doc.public) &&
|
||||||
|
doc.readersCanCopy
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Meteor.Error('Copy permission denied',
|
||||||
|
'You do not have permission to copy this document');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getRoot(doc) {
|
function getRoot(doc) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
||||||
@@ -79,6 +116,17 @@ export function assertDocEditPermission(doc, userId){
|
|||||||
assertEditPermission(root, userId);
|
assertEditPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can copy a descendant document whose root ancestor
|
||||||
|
* implements sharing permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertDocCopyPermission(doc, userId) {
|
||||||
|
let root = getRoot(doc);
|
||||||
|
assertCopyPermission(root, userId);
|
||||||
|
}
|
||||||
|
|
||||||
export function assertViewPermission(doc, userId) {
|
export function assertViewPermission(doc, userId) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.public) return true;
|
if (doc.public) return true;
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ const PROPERTIES = Object.freeze({
|
|||||||
action: {
|
action: {
|
||||||
icon: '$vuetify.icons.action',
|
icon: '$vuetify.icons.action',
|
||||||
name: 'Action',
|
name: 'Action',
|
||||||
|
docsPath: 'property/action',
|
||||||
helpText: 'Actions are things your character can do. When an action is taken, all the properties under it are activated.',
|
helpText: 'Actions are things your character can do. When an action is taken, all the properties under it are activated.',
|
||||||
suggestedParents: ['classLevel', 'feature', 'item'],
|
suggestedParents: ['classLevel', 'feature', 'item'],
|
||||||
},
|
},
|
||||||
attribute: {
|
attribute: {
|
||||||
icon: '$vuetify.icons.attribute',
|
icon: '$vuetify.icons.attribute',
|
||||||
name: 'Attribute',
|
name: 'Attribute',
|
||||||
|
docsPath: 'property/attribute',
|
||||||
helpText: 'Attributes are the numbered statistics of your character, excluding rolls you might add proficiency bonus to, those are skills.',
|
helpText: 'Attributes are the numbered statistics of your character, excluding rolls you might add proficiency bonus to, those are skills.',
|
||||||
examples: 'Ability scores, speed, hit points, ki',
|
examples: 'Ability scores, speed, hit points, ki',
|
||||||
suggestedParents: ['classLevel', 'buff'],
|
suggestedParents: ['classLevel', 'buff'],
|
||||||
@@ -15,48 +17,56 @@ const PROPERTIES = Object.freeze({
|
|||||||
adjustment: {
|
adjustment: {
|
||||||
icon: '$vuetify.icons.attribute_damage',
|
icon: '$vuetify.icons.attribute_damage',
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
|
docsPath: 'property/attribute-damage',
|
||||||
helpText: 'Attribute damage reduces the current value of an attribute when it is applied by an action. A negative value causes the attribute to increase instead, up to its normal maximum.',
|
helpText: 'Attribute damage reduces the current value of an attribute when it is applied by an action. A negative value causes the attribute to increase instead, up to its normal maximum.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
buff: {
|
buff: {
|
||||||
icon: '$vuetify.icons.buff',
|
icon: '$vuetify.icons.buff',
|
||||||
name: 'Buff',
|
name: 'Buff',
|
||||||
|
docsPath: 'property/buff',
|
||||||
helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.',
|
helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
buffRemover: {
|
buffRemover: {
|
||||||
icon: '$vuetify.icons.buffRemover',
|
icon: '$vuetify.icons.buffRemover',
|
||||||
name: 'Remove Buff',
|
name: 'Remove Buff',
|
||||||
|
docsPath: 'property/remove-buff',
|
||||||
helpText: 'Removes a buff from the target character',
|
helpText: 'Removes a buff from the target character',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
branch: {
|
branch: {
|
||||||
icon: 'mdi-file-tree',
|
icon: 'mdi-file-tree',
|
||||||
name: 'Branch',
|
name: 'Branch',
|
||||||
|
docsPath: 'property/branch',
|
||||||
helpText: 'When a branch is activated as a child of an action, it can control which of its children get activated.',
|
helpText: 'When a branch is activated as a child of an action, it can control which of its children get activated.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell'],
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
icon: 'mdi-card-account-details',
|
icon: 'mdi-card-account-details',
|
||||||
name: 'Class',
|
name: 'Class',
|
||||||
|
docsPath: 'property/class',
|
||||||
helpText: 'Your character should ideally have one starting class. Classes hold class levels',
|
helpText: 'Your character should ideally have one starting class. Classes hold class levels',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
classLevel: {
|
classLevel: {
|
||||||
icon: '$vuetify.icons.class_level',
|
icon: '$vuetify.icons.class_level',
|
||||||
name: 'Class level',
|
name: 'Class level',
|
||||||
|
docsPath: 'property/class-level',
|
||||||
helpText: 'Class levels represent a single level gained in a class',
|
helpText: 'Class levels represent a single level gained in a class',
|
||||||
suggestedParents: ['class'],
|
suggestedParents: ['class'],
|
||||||
},
|
},
|
||||||
constant: {
|
constant: {
|
||||||
icon: 'mdi-anchor',
|
icon: 'mdi-anchor',
|
||||||
name: 'Constant',
|
name: 'Constant',
|
||||||
|
docsPath: 'property/constant',
|
||||||
helpText: 'A constant can define a static value that can be used in calculations elsewhere in the sheet',
|
helpText: 'A constant can define a static value that can be used in calculations elsewhere in the sheet',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
icon: 'mdi-bag-personal-outline',
|
icon: 'mdi-bag-personal-outline',
|
||||||
name: 'Container',
|
name: 'Container',
|
||||||
|
docsPath: 'property/container',
|
||||||
helpText: 'A container holds items in the inventory',
|
helpText: 'A container holds items in the inventory',
|
||||||
examples: 'Coin pouch, backpack',
|
examples: 'Coin pouch, backpack',
|
||||||
suggestedParents: ['folder'],
|
suggestedParents: ['folder'],
|
||||||
@@ -64,18 +74,21 @@ const PROPERTIES = Object.freeze({
|
|||||||
damage: {
|
damage: {
|
||||||
icon: '$vuetify.icons.damage',
|
icon: '$vuetify.icons.damage',
|
||||||
name: 'Damage',
|
name: 'Damage',
|
||||||
|
docsPath: 'property/damage',
|
||||||
helpText: 'When damage is activated by an action it reduces the hit points of the target creature by the calculated amount.',
|
helpText: 'When damage is activated by an action it reduces the hit points of the target creature by the calculated amount.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
damageMultiplier: {
|
damageMultiplier: {
|
||||||
icon: '$vuetify.icons.damage_multiplier',
|
icon: '$vuetify.icons.damage_multiplier',
|
||||||
name: 'Damage multiplier',
|
name: 'Damage multiplier',
|
||||||
|
docsPath: 'property/damage-multiplier',
|
||||||
helpText: 'Resistance, vulnerability, and immunity.',
|
helpText: 'Resistance, vulnerability, and immunity.',
|
||||||
suggestedParents: ['classLevel', 'feature', 'item'],
|
suggestedParents: ['classLevel', 'feature', 'item'],
|
||||||
},
|
},
|
||||||
effect: {
|
effect: {
|
||||||
icon: '$vuetify.icons.effect',
|
icon: '$vuetify.icons.effect',
|
||||||
name: 'Effect',
|
name: 'Effect',
|
||||||
|
docsPath: 'property/effect',
|
||||||
helpText: 'Effects change the value or state of attributes and skills.',
|
helpText: 'Effects change the value or state of attributes and skills.',
|
||||||
examples: '+2 Strength, Advantage on dexterity saving throws',
|
examples: '+2 Strength, Advantage on dexterity saving throws',
|
||||||
suggestedParents: ['buff', 'classLevel', 'feature', 'folder', 'item'],
|
suggestedParents: ['buff', 'classLevel', 'feature', 'folder', 'item'],
|
||||||
@@ -83,36 +96,49 @@ const PROPERTIES = Object.freeze({
|
|||||||
feature: {
|
feature: {
|
||||||
icon: 'mdi-text-subject',
|
icon: 'mdi-text-subject',
|
||||||
name: 'Feature',
|
name: 'Feature',
|
||||||
|
docsPath: 'property/feature',
|
||||||
helpText: 'Descriptive or narrative features your character has access to',
|
helpText: 'Descriptive or narrative features your character has access to',
|
||||||
suggestedParents: ['classLevel', 'folder'],
|
suggestedParents: ['classLevel', 'folder'],
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
icon: 'mdi-folder-outline',
|
icon: 'mdi-folder-outline',
|
||||||
name: 'Folder',
|
name: 'Folder',
|
||||||
|
docsPath: 'property/feature',
|
||||||
helpText: 'A way to organise other properties on the character',
|
helpText: 'A way to organise other properties on the character',
|
||||||
suggestedParents: ['folder'],
|
suggestedParents: ['action', 'folder'],
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
icon: 'mdi-cube-outline',
|
icon: 'mdi-cube-outline',
|
||||||
name: 'Item',
|
name: 'Item',
|
||||||
|
docsPath: 'property/item',
|
||||||
helpText: 'Objects and equipment your charcter finds on their adventures',
|
helpText: 'Objects and equipment your charcter finds on their adventures',
|
||||||
suggestedParents: ['container'],
|
suggestedParents: ['container'],
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
icon: 'mdi-note-outline',
|
icon: 'mdi-note-outline',
|
||||||
name: 'Note',
|
name: 'Note',
|
||||||
|
docsPath: 'property/note',
|
||||||
helpText: 'Notes about your character and their adventures',
|
helpText: 'Notes about your character and their adventures',
|
||||||
suggestedParents: ['folder'],
|
suggestedParents: ['note', 'folder'],
|
||||||
|
},
|
||||||
|
pointBuy: {
|
||||||
|
icon: 'mdi-table',
|
||||||
|
name: 'Point Buy',
|
||||||
|
docsPath: 'property/point-buy',
|
||||||
|
helpText: 'A point buy table that allows the user to select an array of values that match a given cost',
|
||||||
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
proficiency: {
|
proficiency: {
|
||||||
icon: 'mdi-brightness-1',
|
icon: 'mdi-brightness-1',
|
||||||
name: 'Proficiency',
|
name: 'Proficiency',
|
||||||
|
docsPath: 'property/proficiency',
|
||||||
helpText: 'Proficiencies apply your proficiency bonus to skills already on your character sheet.',
|
helpText: 'Proficiencies apply your proficiency bonus to skills already on your character sheet.',
|
||||||
suggestedParents: ['buff', 'classLevel', 'feature', 'folder'],
|
suggestedParents: ['buff', 'classLevel', 'feature', 'folder'],
|
||||||
},
|
},
|
||||||
roll: {
|
roll: {
|
||||||
icon: '$vuetify.icons.roll',
|
icon: '$vuetify.icons.roll',
|
||||||
name: 'Roll',
|
name: 'Roll',
|
||||||
|
docsPath: 'property/roll',
|
||||||
helpText: 'When activated by an action, rolls perform a calculation and temporarily store the result for other properties under the same action to use',
|
helpText: 'When activated by an action, rolls perform a calculation and temporarily store the result for other properties under the same action to use',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
@@ -126,48 +152,56 @@ const PROPERTIES = Object.freeze({
|
|||||||
savingThrow: {
|
savingThrow: {
|
||||||
icon: '$vuetify.icons.saving_throw',
|
icon: '$vuetify.icons.saving_throw',
|
||||||
name: 'Saving throw',
|
name: 'Saving throw',
|
||||||
|
docsPath: 'property/saving-throw',
|
||||||
helpText: 'When a saving throw is activated by an action, it causes the target to make a saving throw, if the saving throw fails, the children properties of the saving throw are activated.',
|
helpText: 'When a saving throw is activated by an action, it causes the target to make a saving throw, if the saving throw fails, the children properties of the saving throw are activated.',
|
||||||
suggestedParents: ['action', 'attack', 'spell'],
|
suggestedParents: ['action', 'attack', 'spell'],
|
||||||
},
|
},
|
||||||
skill: {
|
skill: {
|
||||||
icon: '$vuetify.icons.skill',
|
icon: '$vuetify.icons.skill',
|
||||||
name: 'Skill',
|
name: 'Skill',
|
||||||
|
docsPath: 'property/skill',
|
||||||
helpText: 'Skills, saves, languages, and weapon and tool proficiencies are all skills. Skills can have a default proficiency set. Proficiencies and effects can change the value and state of skills.',
|
helpText: 'Skills, saves, languages, and weapon and tool proficiencies are all skills. Skills can have a default proficiency set. Proficiencies and effects can change the value and state of skills.',
|
||||||
suggestedParents: ['classLevel', 'folder'],
|
suggestedParents: ['classLevel', 'folder'],
|
||||||
},
|
},
|
||||||
propertySlot: {
|
propertySlot: {
|
||||||
icon: 'mdi-power-socket-eu',
|
icon: 'mdi-power-socket-eu',
|
||||||
name: 'Slot',
|
name: 'Slot',
|
||||||
|
docsPath: 'property/slot',
|
||||||
helpText: 'A slot in the character sheet is used to specify that a property needs to be selected from a library to fill the slot. The slot can determine what tags it is looking for, and any subscribed library property with matching tags can fill the slot',
|
helpText: 'A slot in the character sheet is used to specify that a property needs to be selected from a library to fill the slot. The slot can determine what tags it is looking for, and any subscribed library property with matching tags can fill the slot',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
slotFiller: {
|
slotFiller: {
|
||||||
icon: 'mdi-power-plug-outline',
|
icon: 'mdi-power-plug-outline',
|
||||||
name: 'Slot filler',
|
name: 'Slot filler',
|
||||||
|
docsPath: 'property/slot-filler',
|
||||||
helpText: 'A slot filler allows for more advanced logic when it attempts to fill a slot. It can masquarade as any property type, and calculate whether it should fill a slot or not.',
|
helpText: 'A slot filler allows for more advanced logic when it attempts to fill a slot. It can masquarade as any property type, and calculate whether it should fill a slot or not.',
|
||||||
suggestedParents: ['propertySlot'],
|
suggestedParents: ['propertySlot'],
|
||||||
},
|
},
|
||||||
spellList: {
|
spellList: {
|
||||||
icon: '$vuetify.icons.spell_list',
|
icon: '$vuetify.icons.spell_list',
|
||||||
name: 'Spell list',
|
name: 'Spell list',
|
||||||
|
docsPath: 'property/spell-list',
|
||||||
helpText: 'A list of spells on your character sheet. It can provide a DC and spell attack bonus to the spells within',
|
helpText: 'A list of spells on your character sheet. It can provide a DC and spell attack bonus to the spells within',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
spell: {
|
spell: {
|
||||||
icon: '$vuetify.icons.spell',
|
icon: '$vuetify.icons.spell',
|
||||||
name: 'Spell',
|
name: 'Spell',
|
||||||
|
docsPath: 'property/spell',
|
||||||
helpText: 'A spell your character can potentially cast',
|
helpText: 'A spell your character can potentially cast',
|
||||||
suggestedParents: ['spellList'],
|
suggestedParents: ['spellList'],
|
||||||
},
|
},
|
||||||
toggle: {
|
toggle: {
|
||||||
icon: '$vuetify.icons.toggle',
|
icon: '$vuetify.icons.toggle',
|
||||||
name: 'Toggle',
|
name: 'Toggle',
|
||||||
|
docsPath: 'property/toggle',
|
||||||
helpText: 'Togggles allow parts of the character sheet to be turned on and off, either manually or as the result of a calculation.',
|
helpText: 'Togggles allow parts of the character sheet to be turned on and off, either manually or as the result of a calculation.',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
icon: 'mdi-electric-switch',
|
icon: 'mdi-electric-switch',
|
||||||
name: 'Trigger',
|
name: 'Trigger',
|
||||||
|
docsPath: 'property/trigger',
|
||||||
helpText: 'Triggers apply their children in response to events on the character sheet, such as taking an action or receiving damage',
|
helpText: 'Triggers apply their children in response to events on the character sheet, such as taking an action or receiving damage',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
@@ -182,3 +216,17 @@ export function getPropertyName(type){
|
|||||||
export function getPropertyIcon(type){
|
export function getPropertyIcon(type){
|
||||||
return type && PROPERTIES[type] && PROPERTIES[type].icon;
|
return type && PROPERTIES[type] && PROPERTIES[type].icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const propsByDocsPath = new Map();
|
||||||
|
|
||||||
|
for (const key in PROPERTIES) {
|
||||||
|
const prop = PROPERTIES[key];
|
||||||
|
if (prop.docsPath) {
|
||||||
|
propsByDocsPath.set(prop.docsPath, {
|
||||||
|
...prop,
|
||||||
|
type: key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { propsByDocsPath };
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const STORAGE_LIMITS = Object.freeze({
|
|||||||
tagCount: 64,
|
tagCount: 64,
|
||||||
writersCount: 20,
|
writersCount: 20,
|
||||||
libraryCollectionCount: 32,
|
libraryCollectionCount: 32,
|
||||||
|
pointBuyRowsCount: 32,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default STORAGE_LIMITS;
|
export default STORAGE_LIMITS;
|
||||||
|
|||||||
@@ -112,10 +112,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'resolve': {
|
'resolve': {
|
||||||
comment: 'Forces the given calcultion to resolve into a number',
|
comment: 'Forces the given calcultion to resolve into a number, even in calculations where it would usually keep the unknown values as is',
|
||||||
examples: [
|
examples: [
|
||||||
{input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'},
|
{input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'},
|
||||||
{input: 'resolve(3d6)', result: '2'},
|
{input: 'resolve(1d6)', result: '4'},
|
||||||
],
|
],
|
||||||
arguments: ['parseNode'],
|
arguments: ['parseNode'],
|
||||||
fn: function resolveFn(node){
|
fn: function resolveFn(node){
|
||||||
|
|||||||
0
app/imports/server/action.js
Normal file
0
app/imports/server/action.js
Normal file
35
app/imports/server/publications/docs.js
Normal file
35
app/imports/server/publications/docs.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { propsByDocsPath } from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
|
|
||||||
|
// Manual doc paths
|
||||||
|
const docPaths = [
|
||||||
|
'computed-fields',
|
||||||
|
'inline-calculations',
|
||||||
|
'dependency-loops',
|
||||||
|
'docs',
|
||||||
|
'tags',
|
||||||
|
'walkthroughs/create-a-class',
|
||||||
|
];
|
||||||
|
const docs = new Map();
|
||||||
|
docPaths.forEach(path => {
|
||||||
|
docs.set(path, Assets.getText(`docs/${path}.md`))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Doc paths for properties
|
||||||
|
propsByDocsPath.forEach(prop => {
|
||||||
|
docs.set(prop.docsPath, Assets.getText(`docs/${prop.docsPath}.md`));
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish('docs', function (path) {
|
||||||
|
if (!path) {
|
||||||
|
docs.forEach((text, path) => {
|
||||||
|
this.added('docs', path, { text });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const text = docs.get(path);
|
||||||
|
if (text) {
|
||||||
|
this.added('docs', path, { text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ready();
|
||||||
|
});
|
||||||
@@ -11,3 +11,4 @@ import '/imports/server/publications/ownedDocuments.js';
|
|||||||
import '/imports/server/publications/searchLibraryNodes.js';
|
import '/imports/server/publications/searchLibraryNodes.js';
|
||||||
import '/imports/server/publications/archiveFiles.js';
|
import '/imports/server/publications/archiveFiles.js';
|
||||||
import '/imports/server/publications/userImages.js';
|
import '/imports/server/publications/userImages.js';
|
||||||
|
import '/imports/server/publications/docs.js';
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import Invites from '/imports/api/users/Invites.js';
|
|||||||
|
|
||||||
Meteor.publish('user', function () {
|
Meteor.publish('user', function () {
|
||||||
return [
|
return [
|
||||||
Meteor.users.find(this.userId, {fields: {
|
Meteor.users.find(this.userId, {
|
||||||
|
fields: {
|
||||||
roles: 1,
|
roles: 1,
|
||||||
username: 1,
|
username: 1,
|
||||||
apiKey: 1,
|
apiKey: 1,
|
||||||
@@ -22,7 +23,8 @@ Meteor.publish('user', function(){
|
|||||||
'services.google.name': 1,
|
'services.google.name': 1,
|
||||||
'services.google.email': 1,
|
'services.google.email': 1,
|
||||||
'services.google.locale': 1,
|
'services.google.locale': 1,
|
||||||
}}),
|
}
|
||||||
|
}),
|
||||||
Invites.find({
|
Invites.find({
|
||||||
$or: [
|
$or: [
|
||||||
{ inviter: this.userId },
|
{ inviter: this.userId },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
|
||||||
import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||||
import VERSION from '/imports/constants/VERSION.js';
|
import VERSION from '/imports/constants/VERSION.js';
|
||||||
@@ -40,6 +41,9 @@ Meteor.publish('api-creature', function(creatureId){
|
|||||||
CreatureProperties.find({
|
CreatureProperties.find({
|
||||||
'ancestors.id': creatureId,
|
'ancestors.id': creatureId,
|
||||||
}),
|
}),
|
||||||
|
CreatureVariables.find({
|
||||||
|
_creatureId: creatureId,
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}, {
|
}, {
|
||||||
url: 'api/creature/:0'
|
url: 'api/creature/:0'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
:outlined="!!label"
|
:outlined="!!label"
|
||||||
:icon="!label"
|
:icon="!label"
|
||||||
:min-width="label && 108"
|
:min-width="label && 108"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -124,6 +125,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
//hex string
|
//hex string
|
||||||
value: {
|
value: {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default {
|
|||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-layout.wide-columns {
|
.column-layout.wide-columns {
|
||||||
column-count: 12;
|
column-count: 12;
|
||||||
column-fill: balance;
|
column-fill: balance;
|
||||||
@@ -35,14 +36,18 @@ export default {
|
|||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
.column-layout > div, .column-layout > span > div {
|
|
||||||
|
.column-layout>div,
|
||||||
|
.column-layout>span>div {
|
||||||
/*
|
/*
|
||||||
Table and width set because firefox does not support break-inside: avoid
|
Table and width set because firefox does not support break-inside: avoid
|
||||||
*/
|
*/
|
||||||
display: table;
|
display: table;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
-webkit-backface-visibility: hidden;
|
-webkit-backface-visibility: hidden;
|
||||||
|
transform: translateX(0);
|
||||||
-webkit-transform: translateX(0);
|
-webkit-transform: translateX(0);
|
||||||
-webkit-column-break-inside: avoid;
|
-webkit-column-break-inside: avoid;
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
.filled.theme--light {
|
.filled.theme--light {
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filled.theme--dark {
|
.filled.theme--dark {
|
||||||
background: #424242 !important;
|
background: #424242 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<div
|
<div
|
||||||
class="markdown"
|
class="markdown"
|
||||||
|
@click="e => $emit('click', e)"
|
||||||
v-html="compiledMarkdown"
|
v-html="compiledMarkdown"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -44,9 +44,11 @@
|
|||||||
},
|
},
|
||||||
transparentToolbar: Boolean,
|
transparentToolbar: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
hovering: false,
|
hovering: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isDark() {
|
isDark() {
|
||||||
return isDarkColor(this.color);
|
return isDarkColor(this.color);
|
||||||
@@ -72,9 +74,11 @@
|
|||||||
.toolbar-card .v-toolbar__title {
|
.toolbar-card .v-toolbar__title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-card {
|
.toolbar-card {
|
||||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import { format } from 'date-fns';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [SmartInput],
|
mixins: [SmartInput],
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedSafeValue() {
|
formattedSafeValue() {
|
||||||
return format(this.safeValue, 'YYYY-MM-DD')
|
return format(this.safeValue, 'YYYY-MM-DD')
|
||||||
@@ -53,4 +55,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -97,11 +97,13 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
searchString: '',
|
searchString: '',
|
||||||
icons: [],
|
icons: [],
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
menu(value) {
|
menu(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -131,4 +133,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
72
app/imports/ui/components/global/SmartBtn.vue
Normal file
72
app/imports/ui/components/global/SmartBtn.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-btn
|
||||||
|
v-bind="$attrs"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:loading="loading"
|
||||||
|
@click="click"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: Boolean,
|
||||||
|
debounce: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
timesClicked: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isDisabled(){
|
||||||
|
return this.context.editPermission === false || this.disabled;
|
||||||
|
},
|
||||||
|
debounceTime() {
|
||||||
|
if (Number.isFinite(this.debounce)){
|
||||||
|
return this.debounce;
|
||||||
|
} else if (Number.isFinite(this.context.debounceTime)){
|
||||||
|
return this.context.debounceTime;
|
||||||
|
} else {
|
||||||
|
return 750;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.debounceClicks = debounce(this.clicks, this.debounceTime);
|
||||||
|
},
|
||||||
|
beforeDestroy(){
|
||||||
|
this.debounceClicks.flush();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
this.timesClicked += 1;
|
||||||
|
this.debounceClicks();
|
||||||
|
this.$emit('click', this.acknowledgeChange);
|
||||||
|
},
|
||||||
|
clicks() {
|
||||||
|
this.$emit('clicks', this.timesClicked, this.acknowledgeChange);
|
||||||
|
this.loading = true;
|
||||||
|
this.timesClicked = 0;
|
||||||
|
},
|
||||||
|
acknowledgeChange(error){
|
||||||
|
this.loading = false;
|
||||||
|
if (error) {
|
||||||
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -28,9 +28,11 @@
|
|||||||
props: {
|
props: {
|
||||||
multiple: Boolean,
|
multiple: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
searchInput: '',
|
searchInput: '',
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// Multiple combobox gets a long default debounce time while single
|
// Multiple combobox gets a long default debounce time while single
|
||||||
// value gets a shorter one
|
// value gets a shorter one
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export default {
|
|||||||
context: { default: {} }
|
context: { default: {} }
|
||||||
},
|
},
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
ackErrors: null,
|
ackErrors: null,
|
||||||
rulesErrors: null,
|
rulesErrors: null,
|
||||||
@@ -22,7 +23,8 @@ export default {
|
|||||||
dirty: false,
|
dirty: false,
|
||||||
safeValue: this.value,
|
safeValue: this.value,
|
||||||
inputValue: this.value,
|
inputValue: this.value,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
value: [String, Number, Date, Array, Object, Boolean],
|
value: [String, Number, Date, Array, Object, Boolean],
|
||||||
errorMessages: [String, Array],
|
errorMessages: [String, Array],
|
||||||
@@ -115,7 +117,7 @@ export default {
|
|||||||
},
|
},
|
||||||
change(val) {
|
change(val) {
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
if (this.hasChangeListener) this.loading = true;
|
if (this.hasChangeListener()) this.loading = true;
|
||||||
this.$emit('change', val, this.acknowledgeChange);
|
this.$emit('change', val, this.acknowledgeChange);
|
||||||
},
|
},
|
||||||
hasChangeListener() {
|
hasChangeListener() {
|
||||||
|
|||||||
34
app/imports/ui/components/global/SmartSlider.vue
Normal file
34
app/imports/ui/components/global/SmartSlider.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-slider
|
||||||
|
ref="input"
|
||||||
|
v-bind="$attrs"
|
||||||
|
class="dc-text-field"
|
||||||
|
:hide-details="!(errors && errors.length)"
|
||||||
|
:loading="loading"
|
||||||
|
:error-messages="errors"
|
||||||
|
:value="safeValue"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:outlined="!regular"
|
||||||
|
@change="change"
|
||||||
|
@focus="focused = true"
|
||||||
|
@blur="focused = false"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<slot name="prepend" />
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<slot name="append" />
|
||||||
|
</template>
|
||||||
|
</v-slider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [SmartInput],
|
||||||
|
props: {
|
||||||
|
regular: Boolean,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -5,17 +5,21 @@ import IconPicker from '/imports/ui/components/global/IconPicker.vue';
|
|||||||
import TextField from '/imports/ui/components/global/TextField.vue';
|
import TextField from '/imports/ui/components/global/TextField.vue';
|
||||||
import TextArea from '/imports/ui/components/global/TextArea.vue';
|
import TextArea from '/imports/ui/components/global/TextArea.vue';
|
||||||
import SmartSelect from '/imports/ui/components/global/SmartSelect.vue';
|
import SmartSelect from '/imports/ui/components/global/SmartSelect.vue';
|
||||||
|
import SmartBtn from '/imports/ui/components/global/SmartBtn.vue';
|
||||||
import SmartCombobox from '/imports/ui/components/global/SmartCombobox.vue';
|
import SmartCombobox from '/imports/ui/components/global/SmartCombobox.vue';
|
||||||
import SmartCheckbox from '/imports/ui/components/global/SmartCheckbox.vue';
|
import SmartCheckbox from '/imports/ui/components/global/SmartCheckbox.vue';
|
||||||
import SmartSwitch from '/imports/ui/components/global/SmartSwitch.vue';
|
import SmartSwitch from '/imports/ui/components/global/SmartSwitch.vue';
|
||||||
import SvgIcon from '/imports/ui/components/global/SvgIcon.vue';
|
import SvgIcon from '/imports/ui/components/global/SvgIcon.vue';
|
||||||
|
import SmartSlider from '/imports/ui/components/global/SmartSlider.vue';
|
||||||
|
|
||||||
Vue.component('DatePicker', DatePicker);
|
Vue.component('DatePicker', DatePicker);
|
||||||
Vue.component('IconPicker', IconPicker);
|
Vue.component('IconPicker', IconPicker);
|
||||||
Vue.component('TextField', TextField);
|
Vue.component('TextField', TextField);
|
||||||
Vue.component('TextArea', TextArea);
|
Vue.component('TextArea', TextArea);
|
||||||
Vue.component('SmartSelect', SmartSelect);
|
Vue.component('SmartSelect', SmartSelect);
|
||||||
|
Vue.component('SmartBtn', SmartBtn);
|
||||||
Vue.component('SmartCombobox', SmartCombobox);
|
Vue.component('SmartCombobox', SmartCombobox);
|
||||||
Vue.component('SmartCheckbox', SmartCheckbox);
|
Vue.component('SmartCheckbox', SmartCheckbox);
|
||||||
|
Vue.component('SmartSlider', SmartSlider);
|
||||||
Vue.component('SmartSwitch', SmartSwitch);
|
Vue.component('SmartSwitch', SmartSwitch);
|
||||||
Vue.component('SvgIcon', SvgIcon);
|
Vue.component('SvgIcon', SvgIcon);
|
||||||
|
|||||||
@@ -54,8 +54,22 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
v-if="docsPath"
|
||||||
|
@click="helpDialog"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
Help
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-icon>mdi-help</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.duplicate"
|
v-if="$listeners && $listeners.duplicate"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('duplicate')"
|
@click="$emit('duplicate')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -67,8 +81,23 @@
|
|||||||
<v-icon>mdi-content-copy</v-icon>
|
<v-icon>mdi-content-copy</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="$listeners && $listeners.copy"
|
||||||
|
:disabled="context.copyPermission === false"
|
||||||
|
@click="$emit('copy')"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
Copy To
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-icon>mdi-content-duplicate</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.move"
|
v-if="$listeners && $listeners.move"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('move')"
|
@click="$emit('move')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -82,6 +111,7 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.remove"
|
v-if="$listeners && $listeners.remove"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('remove')"
|
@click="$emit('remove')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -137,12 +167,16 @@ import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
|||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
||||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||||
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PropertyIcon,
|
PropertyIcon,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
},
|
},
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -171,7 +205,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return model.name || getPropertyName(model.type);
|
return model.name || getPropertyName(model.type);
|
||||||
}
|
},
|
||||||
|
docsPath() {
|
||||||
|
const propDef = PROPERTIES[this.model.type];
|
||||||
|
return propDef && propDef.docsPath;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
colorChanged(value){
|
colorChanged(value){
|
||||||
@@ -180,6 +218,15 @@ export default {
|
|||||||
back(){
|
back(){
|
||||||
this.$store.dispatch('popDialogStack');
|
this.$store.dispatch('popDialogStack');
|
||||||
},
|
},
|
||||||
|
helpDialog() {
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'help-dialog',
|
||||||
|
elementId: 'property-toolbar-menu-button',
|
||||||
|
data: {
|
||||||
|
path: this.docsPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: undefined,
|
||||||
},
|
},
|
||||||
organize: Boolean,
|
organize: Boolean,
|
||||||
children: {
|
children: {
|
||||||
@@ -118,11 +118,13 @@
|
|||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
startExpanded: Boolean,
|
startExpanded: Boolean,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
|
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
|
||||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id) ||
|
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id) ||
|
||||||
false,
|
false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasChildren() {
|
hasChildren() {
|
||||||
return this.children && !!this.children.length || this.lazy && !this.expanded;
|
return this.children && !!this.children.length || this.lazy && !this.expanded;
|
||||||
@@ -168,43 +170,56 @@
|
|||||||
.rotate-90 {
|
.rotate-90 {
|
||||||
transform: rotate(90deg) translateZ(0);
|
transform: rotate(90deg) translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-area {
|
.drag-area {
|
||||||
box-shadow: -2px 0px 0px 0px #808080;
|
box-shadow: -2px 0px 0px 0px #808080;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty .drag-area {
|
.empty .drag-area {
|
||||||
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty .v-btn {
|
.empty .v-btn {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.found {
|
.found {
|
||||||
background: rgba(200, 0, 0, 0.1) !important;
|
background: rgba(200, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost {
|
.ghost {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background: rgba(251, 0, 0, 0.3);
|
background: rgba(251, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-icon.v-icon--disabled {
|
.v-icon.v-icon--disabled {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-icon {
|
.v-icon {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--light .tree-node-title:hover {
|
.theme--light .tree-node-title:hover {
|
||||||
background-color: rgba(0, 0, 0, .04);
|
background-color: rgba(0, 0, 0, .04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark .tree-node-title:hover {
|
.theme--dark .tree-node-title:hover {
|
||||||
background-color: rgba(255, 255, 255, .04);
|
background-color: rgba(255, 255, 255, .04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-title {
|
.tree-node-title {
|
||||||
transition: background ease 0.3s, color ease 0.15s;
|
transition: background ease 0.3s, color ease 0.15s;
|
||||||
}
|
}
|
||||||
.tree-node-title, .dummy-node {
|
|
||||||
|
.tree-node-title,
|
||||||
|
.dummy-node {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,8 +43,14 @@
|
|||||||
TreeNode,
|
TreeNode,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
node: Object,
|
node: {
|
||||||
group: String,
|
type: Object,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
organize: Boolean,
|
organize: Boolean,
|
||||||
lazy: Boolean,
|
lazy: Boolean,
|
||||||
children: {
|
children: {
|
||||||
@@ -61,10 +67,12 @@
|
|||||||
},
|
},
|
||||||
startExpanded: Boolean,
|
startExpanded: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
expanded: this.startExpanded || false,
|
expanded: this.startExpanded || false,
|
||||||
displayedChildren: [],
|
displayedChildren: [],
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasChildren() {
|
hasChildren() {
|
||||||
return this.children && this.children.length;
|
return this.children && this.children.length;
|
||||||
@@ -125,9 +133,11 @@
|
|||||||
.flip-list-leave-active {
|
.flip-list-leave-active {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flip-list-move {
|
.flip-list-move {
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-move {
|
.no-move {
|
||||||
transition: transform 0s;
|
transition: transform 0s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ export default {
|
|||||||
},
|
},
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
},
|
},
|
||||||
data() { return {
|
data() {
|
||||||
|
return {
|
||||||
libraryCollections: this.model.allowedLibraryCollections,
|
libraryCollections: this.model.allowedLibraryCollections,
|
||||||
libraries: this.model.allowedLibraries,
|
libraries: this.model.allowedLibraries,
|
||||||
libraryWriteLoading: false,
|
libraryWriteLoading: false,
|
||||||
@@ -266,4 +267,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<dialog-base v-if="model" :color="model.color">
|
<dialog-base
|
||||||
|
v-if="model"
|
||||||
|
:color="model.color"
|
||||||
|
>
|
||||||
<template slot="toolbar">
|
<template slot="toolbar">
|
||||||
<v-toolbar-title>
|
<v-toolbar-title>
|
||||||
Character Details
|
Character Details
|
||||||
@@ -81,4 +84,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
/>
|
/>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
|
v-if="node.parent.id === parentSlotId"
|
||||||
icon
|
icon
|
||||||
:disabled="context.editPermission === false"
|
:disabled="context.editPermission === false"
|
||||||
@click.stop="remove(node)"
|
@click.stop="remove(node)"
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
<build-tree-node-list
|
<build-tree-node-list
|
||||||
v-if="showExpanded"
|
v-if="showExpanded"
|
||||||
:children="computedChildren"
|
:children="computedChildren"
|
||||||
|
:parent-slot-id="computedSlotId"
|
||||||
@selected="e => $emit('selected', e)"
|
@selected="e => $emit('selected', e)"
|
||||||
/>
|
/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@@ -147,6 +149,10 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
parentSlotId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
@@ -197,6 +203,21 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.children;
|
return this.children;
|
||||||
},
|
},
|
||||||
|
computedSlotId() {
|
||||||
|
if (this.condenseChild) {
|
||||||
|
if (this.children[0].node.type === 'propertySlot') {
|
||||||
|
return this.children[0].node._id;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.node.type === 'propertySlot') {
|
||||||
|
return this.node._id;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
canExpand(){
|
canExpand(){
|
||||||
return !!this.computedChildren.length || this.canFillWithMany;
|
return !!this.computedChildren.length || this.canFillWithMany;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
:key="child.node._id"
|
:key="child.node._id"
|
||||||
:node="child.node"
|
:node="child.node"
|
||||||
:children="child.children"
|
:children="child.children"
|
||||||
|
:parent-slot-id="parentSlotId"
|
||||||
@selected="e => $emit('selected', e)"
|
@selected="e => $emit('selected', e)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,9 +23,15 @@
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
parentSlotId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
},
|
},
|
||||||
data(){ return {
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
Next
|
Next
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
:disabled="biographyAlert"
|
:disabled="!!biographyAlert"
|
||||||
:text="step < 2"
|
:text="step < 2"
|
||||||
:color="step < 2? '' : 'accent'"
|
:color="step < 2? '' : 'accent'"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
inputName: undefined,
|
inputName: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
nameMatch() {
|
nameMatch() {
|
||||||
if (!this.name) return true;
|
if (!this.name) return true;
|
||||||
@@ -76,4 +78,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -12,9 +12,7 @@
|
|||||||
size="64"
|
size="64"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else-if="!creature">
|
||||||
v-else-if="!creature"
|
|
||||||
>
|
|
||||||
<v-layout
|
<v-layout
|
||||||
column
|
column
|
||||||
align-center
|
align-center
|
||||||
@@ -55,9 +53,7 @@
|
|||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<inventory-tab :creature-id="creatureId" />
|
<inventory-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item
|
<v-tab-item v-if="!creature.settings.hideSpellsTab">
|
||||||
v-if="!creature.settings.hideSpellsTab"
|
|
||||||
>
|
|
||||||
<spells-tab :creature-id="creatureId" />
|
<spells-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
@@ -66,9 +62,7 @@
|
|||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<build-tab :creature-id="creatureId" />
|
<build-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item
|
<v-tab-item v-if="creature.settings.showTreeTab">
|
||||||
v-if="creature.settings.showTreeTab"
|
|
||||||
>
|
|
||||||
<tree-tab :creature-id="creatureId" />
|
<tree-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
</v-tabs-items>
|
</v-tabs-items>
|
||||||
|
|||||||
@@ -11,17 +11,13 @@
|
|||||||
dense
|
dense
|
||||||
>
|
>
|
||||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<v-toolbar-title :key="$store.state.pageTitle">
|
<v-toolbar-title :key="$store.state.pageTitle">
|
||||||
{{ $store.state.pageTitle }}
|
{{ $store.state.pageTitle }}
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<v-layout
|
<v-layout
|
||||||
:key="$route.meta.title"
|
:key="$route.meta.title"
|
||||||
class="flex-shrink-0 flex-grow-0"
|
class="flex-shrink-0 flex-grow-0"
|
||||||
@@ -249,9 +245,11 @@ export default {
|
|||||||
.character-sheet-toolbar .v-tabs__container--grow .v-tabs__div {
|
.character-sheet-toolbar .v-tabs__container--grow .v-tabs__div {
|
||||||
max-width: 120px !important;
|
max-width: 120px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sheet-toolbar .v-tabs__bar {
|
.character-sheet-toolbar .v-tabs__bar {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sheet-fab {
|
.character-sheet-fab {
|
||||||
bottom: -24px;
|
bottom: -24px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<v-menu
|
<v-menu
|
||||||
bottom
|
bottom
|
||||||
left
|
left
|
||||||
|
transition="slide-y-transition"
|
||||||
>
|
>
|
||||||
<template #activator="{ on }">
|
<template #activator="{ on }">
|
||||||
<v-badge
|
<v-badge
|
||||||
@@ -48,12 +49,21 @@
|
|||||||
<v-icon class="mr-2">
|
<v-icon class="mr-2">
|
||||||
mdi-file-hidden
|
mdi-file-hidden
|
||||||
</v-icon>
|
</v-icon>
|
||||||
{{ hiddenCount }} hidden {{ hiddenCount > 1 ? 'slots' : 'slot' }}
|
{{ hiddenCount }} hidden {{ hiddenCount > 1 ? 'properties' : 'property' }}
|
||||||
</v-subheader>
|
</v-subheader>
|
||||||
|
<v-list-item
|
||||||
|
v-for="pointBuy in hiddenPointBuys"
|
||||||
|
:key="pointBuy._id"
|
||||||
|
@click="unhideProp(pointBuy._id)"
|
||||||
|
>
|
||||||
|
<v-list-item-title>
|
||||||
|
{{ getPropertyTitle(pointBuy) }}
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="slot in hiddenSlots"
|
v-for="slot in hiddenSlots"
|
||||||
:key="slot._id"
|
:key="slot._id"
|
||||||
@click="unhideSlot(slot._id)"
|
@click="unhideProp(slot._id)"
|
||||||
>
|
>
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
{{ getPropertyTitle(slot) }}
|
{{ getPropertyTitle(slot) }}
|
||||||
@@ -226,7 +236,7 @@ export default {
|
|||||||
].sort((a, b) => a.order - b.order);
|
].sort((a, b) => a.order - b.order);
|
||||||
},
|
},
|
||||||
hiddenCount() {
|
hiddenCount() {
|
||||||
return this.hiddenSlots.length;
|
return this.hiddenSlots.length + this.hiddenPointBuys.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
@@ -236,6 +246,16 @@ export default {
|
|||||||
variables() {
|
variables() {
|
||||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||||
},
|
},
|
||||||
|
hiddenPointBuys() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
type: 'pointBuy',
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
ignored: true,
|
||||||
|
pointsLeft: {$ne: 0},
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
}).fetch();
|
||||||
|
},
|
||||||
hiddenSlots(){
|
hiddenSlots(){
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
type: 'propertySlot',
|
type: 'propertySlot',
|
||||||
@@ -284,7 +304,7 @@ export default {
|
|||||||
slotBuildTree(){
|
slotBuildTree(){
|
||||||
const slots = CreatureProperties.find({
|
const slots = CreatureProperties.find({
|
||||||
'ancestors.id': this.creatureId,
|
'ancestors.id': this.creatureId,
|
||||||
type: 'propertySlot',
|
type: {$in: ['propertySlot', 'pointBuy']},
|
||||||
$or: [
|
$or: [
|
||||||
{'slotCondition.value': {$nin: [false, 0, '']}},
|
{'slotCondition.value': {$nin: [false, 0, '']}},
|
||||||
{'slotCondition.value': {$exists: false}},
|
{'slotCondition.value': {$exists: false}},
|
||||||
@@ -308,16 +328,15 @@ export default {
|
|||||||
]);
|
]);
|
||||||
traverse(tree, (child, parents) => {
|
traverse(tree, (child, parents) => {
|
||||||
const model = child.node;
|
const model = child.node;
|
||||||
const isSlotWithSpace = model.type === 'propertySlot' &&
|
const isSlotWithSpace = model.type === 'propertySlot' && (
|
||||||
model.spaceLeft > 0 ||
|
model.spaceLeft > 0 ||
|
||||||
!model.quantityExpected ||
|
!model.quantityExpected ||
|
||||||
model.quantityExpected.value === 0;
|
model.quantityExpected.value === 0
|
||||||
|
);
|
||||||
if(isSlotWithSpace) {
|
if(isSlotWithSpace) {
|
||||||
model._canFill = true;
|
model._canFill = true;
|
||||||
parents.forEach(node => {
|
parents.forEach(node => {
|
||||||
if (node.node.type === 'propertySlot'){
|
|
||||||
node.node._descendantCanFill = true;
|
node.node._descendantCanFill = true;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -385,7 +404,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getPropertyTitle,
|
getPropertyTitle,
|
||||||
unhideSlot(_id) {
|
unhideProp(_id) {
|
||||||
updateCreatureProperty.call({
|
updateCreatureProperty.call({
|
||||||
_id,
|
_id,
|
||||||
path: ['ignored'],
|
path: ['ignored'],
|
||||||
|
|||||||
@@ -56,4 +56,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,9 +30,7 @@
|
|||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
<coin-value
|
<coin-value :value="variables && variables.valueTotal && variables.valueTotal.value|| 0" />
|
||||||
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
|
|
||||||
/>
|
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
@@ -85,9 +83,7 @@
|
|||||||
v-for="container in containersWithoutAncestorContainers"
|
v-for="container in containersWithoutAncestorContainers"
|
||||||
:key="container._id"
|
:key="container._id"
|
||||||
>
|
>
|
||||||
<container-card
|
<container-card :model="container" />
|
||||||
:model="container"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</column-layout>
|
</column-layout>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,9 +116,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
organize: false,
|
organize: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
containers() {
|
containers() {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
@@ -135,10 +133,12 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
creature() {
|
creature() {
|
||||||
return Creatures.findOne(this.creatureId, {fields: {
|
return Creatures.findOne(this.creatureId, {
|
||||||
|
fields: {
|
||||||
color: 1,
|
color: 1,
|
||||||
variables: 1,
|
variables: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
variables() {
|
variables() {
|
||||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||||
@@ -229,4 +229,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
organize: false,
|
organize: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
spellLists() {
|
spellLists() {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
@@ -103,4 +105,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div
|
<div class="stats-tab ma-2">
|
||||||
class="stats-tab ma-2"
|
|
||||||
>
|
|
||||||
<health-bar-card-container :creature-id="creatureId" />
|
<health-bar-card-container :creature-id="creatureId" />
|
||||||
|
|
||||||
<column-layout>
|
<column-layout>
|
||||||
@@ -171,9 +169,7 @@
|
|||||||
v-if="spellSlots && spellSlots.length || hasSpells"
|
v-if="spellSlots && spellSlots.length || hasSpells"
|
||||||
class="spell-slots"
|
class="spell-slots"
|
||||||
>
|
>
|
||||||
<v-card
|
<v-card data-id="spell-slot-card">
|
||||||
data-id="spell-slot-card"
|
|
||||||
>
|
|
||||||
<v-list
|
<v-list
|
||||||
v-if="spellSlots && spellSlots.length"
|
v-if="spellSlots && spellSlots.length"
|
||||||
two-line
|
two-line
|
||||||
@@ -253,18 +249,6 @@
|
|||||||
@sub-click="_id => clickTreeProperty({_id})"
|
@sub-click="_id => clickTreeProperty({_id})"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-for="attack in attacks"
|
|
||||||
:key="attack._id"
|
|
||||||
class="attack"
|
|
||||||
>
|
|
||||||
<action-card
|
|
||||||
attack
|
|
||||||
:model="attack"
|
|
||||||
:data-id="attack._id"
|
|
||||||
@click="clickProperty({_id: attack._id})"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="weapons && weapons.length"
|
v-if="weapons && weapons.length"
|
||||||
@@ -381,6 +365,10 @@
|
|||||||
filter.removed = { $ne: true };
|
filter.removed = { $ne: true };
|
||||||
filter.inactive = { $ne: true };
|
filter.inactive = { $ne: true };
|
||||||
filter.overridden = { $ne: true };
|
filter.overridden = { $ne: true };
|
||||||
|
filter.$nor = [
|
||||||
|
{ hideWhenTotalZero: true, total: 0 },
|
||||||
|
{ hideWhenValueZero: true, value: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
return CreatureProperties.find(filter, options);
|
return CreatureProperties.find(filter, options);
|
||||||
};
|
};
|
||||||
@@ -420,9 +408,11 @@
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
doCheckLoading: false,
|
doCheckLoading: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
creature() {
|
creature() {
|
||||||
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
||||||
@@ -494,19 +484,6 @@
|
|||||||
type: 'damageMultiplier'
|
type: 'damageMultiplier'
|
||||||
}, {
|
}, {
|
||||||
sort: { value: 1, order: 1 }
|
sort: { value: 1, order: 1 }
|
||||||
});
|
|
||||||
},
|
|
||||||
attacks(){
|
|
||||||
let props = getProperties(this.creature, {type: 'attack'})
|
|
||||||
return props && props.map(attack => {
|
|
||||||
attack.children = CreatureProperties.find({
|
|
||||||
'ancestors.id': attack._id,
|
|
||||||
removed: {$ne: true},
|
|
||||||
inactive: {$ne: true},
|
|
||||||
}, {
|
|
||||||
sort: {order: 1}
|
|
||||||
});
|
|
||||||
return attack;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -542,9 +519,16 @@
|
|||||||
data: {
|
data: {
|
||||||
creatureId: this.creatureId,
|
creatureId: this.creatureId,
|
||||||
},
|
},
|
||||||
callback({spellId, slotId} = {}){
|
callback({ spellId, slotId, advantage, ritual } = {}) {
|
||||||
if (!spellId) return;
|
if (!spellId) return;
|
||||||
doCastSpell.call({spellId, slotId}, error => {
|
doCastSpell.call({
|
||||||
|
spellId,
|
||||||
|
slotId,
|
||||||
|
ritual,
|
||||||
|
scope: {
|
||||||
|
$attackAdvantage: advantage,
|
||||||
|
},
|
||||||
|
}, error => {
|
||||||
if (!error) return;
|
if (!error) return;
|
||||||
snackbar({ text: error.reason || error.message || error.toString() });
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -557,4 +541,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -89,4 +89,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() { return {
|
data() {
|
||||||
|
return {
|
||||||
type: undefined,
|
type: undefined,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyName,
|
getPropertyName,
|
||||||
back() {
|
back() {
|
||||||
@@ -45,4 +47,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,11 +30,14 @@
|
|||||||
DialogBase,
|
DialogBase,
|
||||||
LibraryAndNode,
|
LibraryAndNode,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
node: undefined,
|
node: undefined,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -83,4 +83,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/
|
|||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
||||||
import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue';
|
import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
|
||||||
import { clone } from 'lodash';
|
import { clone } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -217,13 +216,15 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
selectedNodeIds: [],
|
selectedNodeIds: [],
|
||||||
searchInput: undefined,
|
searchInput: undefined,
|
||||||
searchValue: undefined,
|
searchValue: undefined,
|
||||||
showDisabled: false,
|
showDisabled: false,
|
||||||
disabledNodeCount: undefined,
|
disabledNodeCount: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
reactiveProvide: {
|
reactiveProvide: {
|
||||||
name: 'context',
|
name: 'context',
|
||||||
include: ['creatureId'],
|
include: ['creatureId'],
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ export default {
|
|||||||
}},
|
}},
|
||||||
computed: {
|
computed: {
|
||||||
accentColor() {
|
accentColor() {
|
||||||
if (this.theme.isDark){
|
if (this.model.color) {
|
||||||
|
return this.model.color
|
||||||
|
} else if (this.theme.isDark){
|
||||||
return this.$vuetify.theme.themes.dark.primary;
|
return this.$vuetify.theme.themes.dark.primary;
|
||||||
} else {
|
} else {
|
||||||
return this.$vuetify.theme.themes.light.primary;
|
return this.$vuetify.theme.themes.light.primary;
|
||||||
|
|||||||
@@ -5,6 +5,18 @@
|
|||||||
leave-absolute
|
leave-absolute
|
||||||
hide-on-leave
|
hide-on-leave
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-for="pointBuy in pointBuys"
|
||||||
|
:key="pointBuy._id"
|
||||||
|
style="transition: all 0.3s !important"
|
||||||
|
>
|
||||||
|
<point-buy-card
|
||||||
|
:model="pointBuy"
|
||||||
|
hover
|
||||||
|
@ignore="ignoreProp(pointBuy._id)"
|
||||||
|
@click="editPointBuy(pointBuy._id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="slot in slots"
|
v-for="slot in slots"
|
||||||
:key="slot._id"
|
:key="slot._id"
|
||||||
@@ -13,7 +25,7 @@
|
|||||||
<slot-card
|
<slot-card
|
||||||
:model="slot"
|
:model="slot"
|
||||||
hover
|
hover
|
||||||
@ignore="ignoreSlot(slot._id)"
|
@ignore="ignoreProp(slot._id)"
|
||||||
@click="fillSlot(slot._id)"
|
@click="fillSlot(slot._id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,6 +36,7 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import SlotCard from '/imports/ui/creature/slots/SlotCard.vue';
|
import SlotCard from '/imports/ui/creature/slots/SlotCard.vue';
|
||||||
|
import PointBuyCard from '/imports/ui/properties/components/pointBuy/PointBuyCard.vue';
|
||||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
|
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
|
||||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||||
@@ -32,13 +45,14 @@ import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SlotCard,
|
SlotCard,
|
||||||
|
PointBuyCard,
|
||||||
ColumnLayout,
|
ColumnLayout,
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
context: { default: {} }
|
context: { default: {} }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
ignoreSlot(_id){
|
ignoreProp(_id){
|
||||||
updateCreatureProperty.call({
|
updateCreatureProperty.call({
|
||||||
_id,
|
_id,
|
||||||
path: ['ignored'],
|
path: ['ignored'],
|
||||||
@@ -75,6 +89,16 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
editPointBuy(_id){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'creature-property-dialog',
|
||||||
|
elementId: `point-buy-card-${_id}`,
|
||||||
|
data: {
|
||||||
|
_id,
|
||||||
|
startInEditTab: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
slots(){
|
slots(){
|
||||||
@@ -99,7 +123,16 @@ export default {
|
|||||||
removed: {$ne: true},
|
removed: {$ne: true},
|
||||||
inactive: {$ne: true},
|
inactive: {$ne: true},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
pointBuys(){
|
||||||
|
return CreatureProperties.find({
|
||||||
|
type: 'pointBuy',
|
||||||
|
'ancestors.id': this.context.creatureId,
|
||||||
|
ignored: { $ne: true },
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -218,13 +218,15 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
selectedNodeIds: [],
|
selectedNodeIds: [],
|
||||||
searchInput: undefined,
|
searchInput: undefined,
|
||||||
searchValue: undefined,
|
searchValue: undefined,
|
||||||
showDisabled: false,
|
showDisabled: false,
|
||||||
disabledNodeCount: undefined,
|
disabledNodeCount: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
reactiveProvide: {
|
reactiveProvide: {
|
||||||
name: 'context',
|
name: 'context',
|
||||||
include: ['creatureId'],
|
include: ['creatureId'],
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user