Compare commits
11 Commits
2.0-beta.4
...
2.0-beta.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d57a74667 | ||
|
|
21b0029df7 | ||
|
|
c0ccafa787 | ||
|
|
d63ad9ea8f | ||
|
|
8f56a60fb1 | ||
|
|
358ae46627 | ||
|
|
0b1db3c40c | ||
|
|
0ad7e659d2 | ||
|
|
58c3875dc7 | ||
|
|
84f506f1fe | ||
|
|
d0a3ccc76a |
@@ -11,7 +11,7 @@ accounts-google@1.4.0
|
||||
email@2.2.1
|
||||
meteor-base@1.5.1
|
||||
mobile-experience@1.1.0
|
||||
mongo@1.16.0-beta280.7
|
||||
mongo@1.16.0
|
||||
session@1.2.0
|
||||
tracker@1.2.0
|
||||
logging@1.3.1
|
||||
@@ -48,4 +48,4 @@ simple:rest-bearer-token-parser
|
||||
simple:rest-json-error-handler
|
||||
littledata:synced-cron
|
||||
mdg:meteor-apm-agent
|
||||
typescript
|
||||
typescript@4.5.4
|
||||
|
||||
@@ -1 +1 @@
|
||||
METEOR@2.8-beta.7
|
||||
METEOR@2.8.0
|
||||
|
||||
@@ -27,10 +27,10 @@ coffeescript@2.4.1
|
||||
coffeescript-compiler@2.4.1
|
||||
dburles:mongo-collection-instances@0.3.6
|
||||
ddp@1.4.0
|
||||
ddp-client@2.5.0
|
||||
ddp-client@2.6.0
|
||||
ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.1.0
|
||||
ddp-server@2.5.0
|
||||
ddp-server@2.6.0
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.7.2
|
||||
ecmascript@0.16.2
|
||||
@@ -57,7 +57,7 @@ localstorage@1.2.0
|
||||
logging@1.3.1
|
||||
mdg:meteor-apm-agent@3.5.1
|
||||
mdg:validated-method@1.2.0
|
||||
meteor@1.10.1-beta280.7
|
||||
meteor@1.10.1
|
||||
meteor-base@1.5.1
|
||||
meteortesting:browser-tests@1.3.5
|
||||
meteortesting:mocha@2.0.3
|
||||
@@ -65,18 +65,18 @@ meteortesting:mocha-core@8.1.2
|
||||
mikowals:batch-insert@1.3.0
|
||||
minifier-css@1.6.1
|
||||
minifier-js@2.7.5
|
||||
minimongo@1.9.0-beta280.7
|
||||
minimongo@1.9.0
|
||||
mobile-experience@1.1.0
|
||||
mobile-status-bar@1.1.0
|
||||
modern-browsers@0.1.8
|
||||
modules@0.19.0-beta280.7
|
||||
modules@0.19.0
|
||||
modules-runtime@0.13.0
|
||||
mongo@1.16.0-beta280.7
|
||||
mongo@1.16.0
|
||||
mongo-decimal@0.1.3
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.8
|
||||
mongo-livedata@1.0.12
|
||||
npm-mongo@4.9.0-beta280.7
|
||||
npm-mongo@4.9.0
|
||||
oauth@2.1.2
|
||||
oauth2@1.3.1
|
||||
ordered-dict@1.1.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { some, intersection, difference, remove, includes } from 'lodash';
|
||||
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 logErrors from './shared/logErrors.js';
|
||||
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
} from '/imports/api/engine/loadCreatures.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
|
||||
export default function applyDamage(node, actionContext){
|
||||
export default function applyDamage(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
const applyChildren = function(){
|
||||
const applyChildren = function () {
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
node.children.forEach(child => applyProperty(child, actionContext));
|
||||
};
|
||||
@@ -28,10 +28,10 @@ export default function applyDamage(node, actionContext){
|
||||
// Determine if the hit is critical
|
||||
let criticalHit = scope['$criticalHit']?.value &&
|
||||
prop.damageType !== 'healing' // Can't critically heal
|
||||
;
|
||||
;
|
||||
// Double the damage rolls if the hit is critical
|
||||
let context = new Context({
|
||||
options: {doubleRolls: criticalHit},
|
||||
options: { doubleRolls: criticalHit },
|
||||
});
|
||||
|
||||
// Gather all the lines we need to log into an array
|
||||
@@ -40,8 +40,8 @@ export default function applyDamage(node, actionContext){
|
||||
|
||||
// roll the dice only and store that string
|
||||
applyEffectsToCalculationParseNode(prop.amount, actionContext.log);
|
||||
const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context);
|
||||
if (rolled.parseType !== 'constant'){
|
||||
const { result: rolled } = resolve('roll', prop.amount.parseNode, scope, context);
|
||||
if (rolled.parseType !== 'constant') {
|
||||
logValue.push(toString(rolled));
|
||||
}
|
||||
logErrors(context.errors, actionContext);
|
||||
@@ -50,13 +50,13 @@ export default function applyDamage(node, actionContext){
|
||||
context.errors = [];
|
||||
|
||||
// Resolve the roll to a final value
|
||||
const {result: reduced} = resolve('reduce', rolled, scope, context);
|
||||
const { result: reduced } = resolve('reduce', rolled, scope, context);
|
||||
logErrors(context.errors, actionContext);
|
||||
|
||||
// Store the result
|
||||
if (reduced.parseType === 'constant'){
|
||||
if (reduced.parseType === 'constant') {
|
||||
prop.amount.value = reduced.value;
|
||||
} else if (reduced.parseType === 'error'){
|
||||
} else if (reduced.parseType === 'error') {
|
||||
prop.amount.value = null;
|
||||
} else {
|
||||
prop.amount.value = toString(reduced);
|
||||
@@ -64,7 +64,7 @@ export default function applyDamage(node, actionContext){
|
||||
let damage = +reduced.value;
|
||||
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)){
|
||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||
return applyChildren();
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export default function applyDamage(node, actionContext){
|
||||
// Memoise the damage suffix for the log
|
||||
let suffix = (criticalHit ? ' critical ' : ' ') +
|
||||
prop.damageType +
|
||||
(prop.damageType !== 'healing' ? ' damage ': '');
|
||||
(prop.damageType !== 'healing' ? ' damage ' : '');
|
||||
|
||||
if (damageTargets && damageTargets.length) {
|
||||
// Iterate through all the targets
|
||||
@@ -107,7 +107,7 @@ export default function applyDamage(node, actionContext){
|
||||
});
|
||||
|
||||
// Log the damage done
|
||||
if (target._id === actionContext.creature._id){
|
||||
if (target._id === actionContext.creature._id) {
|
||||
// Target is same as self, log damage as such
|
||||
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
||||
} else {
|
||||
@@ -136,33 +136,33 @@ export default function applyDamage(node, actionContext){
|
||||
return applyChildren();
|
||||
}
|
||||
|
||||
function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
||||
function applyDamageMultipliers({ target, damage, damageProp, logValue }) {
|
||||
const damageType = damageProp?.damageType;
|
||||
if (!damageType) return damage;
|
||||
|
||||
const multiplier = target?.variables?.[damageType];
|
||||
if (!multiplier) return damage;
|
||||
|
||||
const damageTypeText = damageType == 'healing' ? 'healing': `${damageType} damage`;
|
||||
const damageTypeText = damageType == 'healing' ? 'healing' : `${damageType} damage`;
|
||||
|
||||
if (
|
||||
multiplier.immunity &&
|
||||
some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity'))
|
||||
){
|
||||
) {
|
||||
logValue.push(`Immune to ${damageTypeText}`);
|
||||
return 0;
|
||||
} else {
|
||||
if (
|
||||
multiplier.resistance &&
|
||||
some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance'))
|
||||
){
|
||||
) {
|
||||
logValue.push(`Resistant to ${damageTypeText}`);
|
||||
damage = Math.floor(damage / 2);
|
||||
}
|
||||
if (
|
||||
multiplier.vulnerability &&
|
||||
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability'))
|
||||
){
|
||||
) {
|
||||
logValue.push(`Vulnerable to ${damageTypeText}`);
|
||||
damage = Math.floor(damage * 2);
|
||||
}
|
||||
@@ -170,7 +170,7 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
||||
return damage;
|
||||
}
|
||||
|
||||
function multiplierAppliesTo(damageProp, multiplierType){
|
||||
function multiplierAppliesTo(damageProp, multiplierType) {
|
||||
return multiplier => {
|
||||
// Apply the default 'ignore x' tags
|
||||
if (includes(damageProp.tags, `ignore ${multiplierType}`)) return false;
|
||||
@@ -187,7 +187,7 @@ function multiplierAppliesTo(damageProp, multiplierType){
|
||||
}
|
||||
}
|
||||
|
||||
function dealDamage({target, damageType, amount, actionContext}){
|
||||
function dealDamage({ target, damageType, amount, actionContext }) {
|
||||
// Get all the health bars and do damage to them
|
||||
let healthBars = getPropertiesOfType(target._id, 'attribute');
|
||||
|
||||
@@ -239,6 +239,14 @@ function dealDamage({target, damageType, amount, actionContext}){
|
||||
actionContext
|
||||
});
|
||||
damageLeft -= damageAdded;
|
||||
// Prevent overflow
|
||||
if (
|
||||
damageType === 'healing' ?
|
||||
healthBar.healthBarNoHealingOverflow :
|
||||
healthBar.healthBarNoDamageOverflow
|
||||
) {
|
||||
damageLeft = 0;
|
||||
}
|
||||
});
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ const doAction = new ValidatedMethod({
|
||||
let slot;
|
||||
|
||||
// If a spell requires a slot, make sure a slot is spent
|
||||
if (!spell.castWithoutSpellSlots && !(ritual && spell.ritual)) {
|
||||
if (spell.level && !spell.castWithoutSpellSlots && !(ritual && spell.ritual)) {
|
||||
slot = CreatureProperties.findOne(slotId);
|
||||
if (!slot) {
|
||||
throw new Meteor.Error('No slot',
|
||||
|
||||
@@ -23,7 +23,7 @@ const doCheck = new ValidatedMethod({
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({propId, scope}) {
|
||||
run({ propId, scope }) {
|
||||
const prop = CreatureProperties.findOne(propId);
|
||||
const creatureId = prop.ancestors[0].id;
|
||||
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||
@@ -33,13 +33,13 @@ const doCheck = new ValidatedMethod({
|
||||
assertEditPermission(actionContext.creature, this.userId);
|
||||
|
||||
// Do the check
|
||||
doCheckWork({prop, actionContext});
|
||||
doCheckWork({ prop, actionContext });
|
||||
},
|
||||
});
|
||||
|
||||
export default doCheck;
|
||||
|
||||
export function doCheckWork({prop, actionContext}){
|
||||
export function doCheckWork({ prop, actionContext }) {
|
||||
|
||||
applyTriggers(actionContext.triggers.check?.before, prop, actionContext);
|
||||
rollCheck(prop, actionContext);
|
||||
@@ -54,17 +54,17 @@ function rollCheck(prop, actionContext) {
|
||||
// get the modifier for the roll
|
||||
let rollModifier;
|
||||
let logName = `${prop.name} check`;
|
||||
if (prop.type === 'skill'){
|
||||
if (prop.type === 'skill') {
|
||||
rollModifier = prop.value;
|
||||
if (prop.skillType === 'save'){
|
||||
if (prop.name.match(/save/i)){
|
||||
if (prop.skillType === 'save') {
|
||||
if (prop.name.match(/save/i)) {
|
||||
logName = prop.name;
|
||||
} else {
|
||||
logName = prop.name ? `${prop.name} save` : 'Saving Throw';
|
||||
}
|
||||
}
|
||||
} else if (prop.type === 'attribute'){
|
||||
if (prop.attributeType === 'ability'){
|
||||
} else if (prop.type === 'attribute') {
|
||||
if (prop.attributeType === 'ability') {
|
||||
rollModifier = prop.modifier;
|
||||
} else {
|
||||
rollModifier = prop.value;
|
||||
@@ -80,7 +80,7 @@ function rollCheck(prop, actionContext) {
|
||||
rollModifier += effectBonus;
|
||||
|
||||
let value, values, resultPrefix;
|
||||
if (scope['$checkAdvantage'] === 1){
|
||||
if (scope['$checkAdvantage'] === 1) {
|
||||
logName += ' (Advantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
@@ -90,7 +90,7 @@ function rollCheck(prop, actionContext) {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else if (scope['$checkAdvantage'] === -1){
|
||||
} else if (scope['$checkAdvantage'] === -1) {
|
||||
logName += ' (Disadvantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
@@ -106,6 +106,9 @@ function rollCheck(prop, actionContext) {
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||
}
|
||||
const result = (value + rollModifier) || 0;
|
||||
scope['$checkDiceRoll'] = value;
|
||||
scope['$checkRoll'] = result;
|
||||
scope['$checkModifier'] = rollModifier;
|
||||
actionContext.addLog({
|
||||
name: logName,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
@@ -116,7 +119,7 @@ function applyUnresolvedEffects(prop, scope) {
|
||||
let effectBonus = 0;
|
||||
let effectString = '';
|
||||
if (!prop.effects) {
|
||||
return { effectBonus, effectString};
|
||||
return { effectBonus, effectString };
|
||||
}
|
||||
prop.effects.forEach(effect => {
|
||||
if (!effect.amount?.parseNode) return;
|
||||
@@ -127,5 +130,5 @@ function applyUnresolvedEffects(prop, scope) {
|
||||
effectBonus += effect.amount.value;
|
||||
effectString += ` ${effect.amount.value < 0 ? '-' : '+'} [${effect.amount.calculation}] ${Math.abs(effect.amount.value)}`
|
||||
});
|
||||
return { effectBonus, effectString};
|
||||
return { effectBonus, effectString };
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const DUPLICATE_CHILDREN_LIMIT = 50;
|
||||
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||
|
||||
const duplicateLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.duplicate',
|
||||
@@ -28,7 +28,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
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/updateReferenceNode.js';
|
||||
|
||||
@@ -69,6 +69,16 @@ let AttributeSchema = createPropertySchema({
|
||||
type: Boolean,
|
||||
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: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
@@ -107,6 +117,14 @@ let AttributeSchema = createPropertySchema({
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
hideWhenTotalZero: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
hideWhenValueZero: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Automatically zero the adjustment on these conditions
|
||||
reset: {
|
||||
type: String,
|
||||
|
||||
@@ -33,6 +33,10 @@ let SharingSchema = new SimpleSchema({
|
||||
defaultValue: false,
|
||||
index: 1,
|
||||
},
|
||||
readersCanCopy: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
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({
|
||||
name: 'sharing.updateUserSharePermissions',
|
||||
validate: new SimpleSchema({
|
||||
@@ -129,4 +149,4 @@ const transferOwnership = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
||||
export { setPublic, setReadersCanCopy, updateUserSharePermissions, transferOwnership };
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { _ } from 'meteor/underscore';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
|
||||
function assertIdValid(userId){
|
||||
if (!userId || typeof userId !== 'string'){
|
||||
function assertIdValid(userId) {
|
||||
if (!userId || typeof userId !== 'string') {
|
||||
throw new Meteor.Error('Permission denied',
|
||||
'No user ID. Are you logged in?');
|
||||
}
|
||||
}
|
||||
|
||||
function assertdocExists(doc){
|
||||
if (!doc){
|
||||
function assertdocExists(doc) {
|
||||
if (!doc) {
|
||||
throw new Meteor.Error('Permission denied',
|
||||
'Permission denied: No such document exists');
|
||||
}
|
||||
}
|
||||
|
||||
export function assertOwnership(doc, userId){
|
||||
export function assertOwnership(doc, userId) {
|
||||
assertIdValid(userId);
|
||||
assertdocExists(doc);
|
||||
if (doc.owner === userId ){
|
||||
|
||||
if (doc.owner === userId) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Meteor.Error('Permission denied',
|
||||
@@ -37,13 +38,12 @@ export function assertEditPermission(doc, userId) {
|
||||
assertdocExists(doc);
|
||||
const user = Meteor.users.findOne(userId, {
|
||||
fields: {
|
||||
'services.patreon': 1,
|
||||
'roles': 1,
|
||||
}
|
||||
});
|
||||
|
||||
// Admin override
|
||||
if (user.roles && user.roles.includes('admin')){
|
||||
if (user.roles && user.roles.includes('admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function assertEditPermission(doc, userId) {
|
||||
if (
|
||||
doc.owner === userId ||
|
||||
_.contains(doc.writers, userId)
|
||||
){
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Meteor.Error('Edit permission denied',
|
||||
@@ -59,9 +59,46 @@ export function assertEditPermission(doc, userId) {
|
||||
}
|
||||
}
|
||||
|
||||
function getRoot(doc){
|
||||
/**
|
||||
* 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);
|
||||
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]){
|
||||
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) {
|
||||
assertdocExists(doc);
|
||||
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
||||
return fetchDocByRef(doc.ancestors[0]);
|
||||
} else {
|
||||
return doc;
|
||||
@@ -74,11 +111,22 @@ function getRoot(doc){
|
||||
*
|
||||
* Warning: the doc and userId must be set by a trusted source
|
||||
*/
|
||||
export function assertDocEditPermission(doc, userId){
|
||||
export function assertDocEditPermission(doc, userId) {
|
||||
let root = getRoot(doc);
|
||||
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) {
|
||||
assertdocExists(doc);
|
||||
if (doc.public) return true;
|
||||
@@ -88,17 +136,17 @@ export function assertViewPermission(doc, userId) {
|
||||
doc.owner === userId ||
|
||||
_.contains(doc.readers, userId) ||
|
||||
_.contains(doc.writers, userId)
|
||||
){
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
|
||||
// Admin override
|
||||
const user = Meteor.users.findOne(userId, {
|
||||
fields: {
|
||||
'roles': 1,
|
||||
}
|
||||
});
|
||||
if (user.roles && user.roles.includes('admin')){
|
||||
if (user.roles && user.roles.includes('admin')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -113,20 +161,20 @@ export function assertViewPermission(doc, userId) {
|
||||
*
|
||||
* Warning: the doc and userId must be set by a trusted source
|
||||
*/
|
||||
export function assertDocViewPermission(doc, userId){
|
||||
export function assertDocViewPermission(doc, userId) {
|
||||
let root = getRoot(doc);
|
||||
assertViewPermission(root, userId);
|
||||
}
|
||||
|
||||
export function assertAdmin(userId){
|
||||
export function assertAdmin(userId) {
|
||||
assertIdValid(userId);
|
||||
let user = Meteor.users.findOne(userId, {fields: {roles: 1}});
|
||||
if (!user){
|
||||
let user = Meteor.users.findOne(userId, { fields: { roles: 1 } });
|
||||
if (!user) {
|
||||
throw new Meteor.Error('Permission denied',
|
||||
'UserId does not match any existing user');
|
||||
}
|
||||
let isAdmin = user.roles && user.roles.includes('admin')
|
||||
if (!isAdmin){
|
||||
if (!isAdmin) {
|
||||
throw new Meteor.Error('Permission denied',
|
||||
'User does not have the admin role');
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const docPaths = [
|
||||
'dependency-loops',
|
||||
'docs',
|
||||
'tags',
|
||||
'walkthroughs/create-a-class',
|
||||
];
|
||||
const docs = new Map();
|
||||
docPaths.forEach(path => {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
:outlined="!!label"
|
||||
:icon="!label"
|
||||
:min-width="label && 108"
|
||||
:disabled="context.editPermission === false"
|
||||
v-on="on"
|
||||
>
|
||||
{{ label }}
|
||||
@@ -124,6 +125,9 @@
|
||||
}
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
//hex string
|
||||
value: {
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-if="$listeners && $listeners.duplicate"
|
||||
:disabled="context.editPermission === false"
|
||||
@click="$emit('duplicate')"
|
||||
>
|
||||
<v-list-item-content>
|
||||
@@ -80,8 +81,23 @@
|
||||
<v-icon>mdi-content-copy</v-icon>
|
||||
</v-list-item-action>
|
||||
</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-if="$listeners && $listeners.move"
|
||||
:disabled="context.editPermission === false"
|
||||
@click="$emit('move')"
|
||||
>
|
||||
<v-list-item-content>
|
||||
@@ -95,6 +111,7 @@
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-if="$listeners && $listeners.remove"
|
||||
:disabled="context.editPermission === false"
|
||||
@click="$emit('remove')"
|
||||
>
|
||||
<v-list-item-content>
|
||||
@@ -157,6 +174,9 @@ export default {
|
||||
PropertyIcon,
|
||||
ColorPicker,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
|
||||
@@ -365,6 +365,10 @@ const getProperties = function (creature, filter, options = {
|
||||
filter.removed = { $ne: true };
|
||||
filter.inactive = { $ne: true };
|
||||
filter.overridden = { $ne: true };
|
||||
filter.$nor = [
|
||||
{ hideWhenTotalZero: true, total: 0 },
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
];
|
||||
|
||||
return CreatureProperties.find(filter, options);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:embedded="embedded"
|
||||
@duplicate="duplicate"
|
||||
@move="move"
|
||||
@copy="copy"
|
||||
@remove="remove"
|
||||
@toggle-editing="editing = !editing"
|
||||
@color-changed="value => change({path: ['color'], value})"
|
||||
@@ -95,10 +96,13 @@
|
||||
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
|
||||
import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js';
|
||||
import { get } from 'lodash';
|
||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import {
|
||||
assertDocEditPermission, assertDocCopyPermission
|
||||
} from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
||||
import copyLibraryNodeTo from '/imports/api/library/methods/copyLibraryNodeTo.js';
|
||||
|
||||
let formIndex = {};
|
||||
for (let key in propertyFormIndex){
|
||||
@@ -126,7 +130,7 @@
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['editPermission', 'isLibraryForm'],
|
||||
include: ['editPermission', 'copyPermission', 'isLibraryForm'],
|
||||
},
|
||||
data(){return {
|
||||
editing: !!this.startInEditTab,
|
||||
@@ -162,6 +166,14 @@
|
||||
return false;
|
||||
}
|
||||
},
|
||||
copyPermission(){
|
||||
try {
|
||||
assertDocCopyPermission(this.model, Meteor.userId());
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyName,
|
||||
@@ -200,6 +212,37 @@
|
||||
}
|
||||
});
|
||||
},
|
||||
copy(){
|
||||
const thisId = this._id;
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'move-library-node-dialog',
|
||||
elementId: 'property-toolbar-menu-button',
|
||||
data: {
|
||||
action: 'Copy',
|
||||
},
|
||||
callback(parentId){
|
||||
if (!parentId) return;
|
||||
copyLibraryNodeTo.call({
|
||||
_id: thisId,
|
||||
parent: {
|
||||
collection: 'libraryNodes',
|
||||
id: parentId
|
||||
},
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
snackbar({
|
||||
text: error.reason || error.message || error.toString(),
|
||||
});
|
||||
} else {
|
||||
snackbar({
|
||||
text: 'Copied successfully',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
change({path, value, ack}){
|
||||
updateLibraryNode.call({_id: this.currentId, path, value}, (error) =>{
|
||||
if (ack){
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
color="primary"
|
||||
@click="$store.dispatch('popDialogStack', node._id)"
|
||||
>
|
||||
Move
|
||||
{{ action || 'Move' }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</dialog-base>
|
||||
@@ -30,6 +30,12 @@ export default {
|
||||
DialogBase,
|
||||
LibraryAndNode,
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
node: undefined,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<v-container class="documentation">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-row justify="center">
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="8"
|
||||
>
|
||||
<v-fade-transition mode="out-in">
|
||||
<v-card
|
||||
v-if="doc"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<v-container class="documentation">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-row justify="center">
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="8"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-text class="markdown">
|
||||
<h1>Functions</h1>
|
||||
|
||||
@@ -42,6 +42,10 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
overridden: { $ne: true },
|
||||
$nor: [
|
||||
{ hideWhenTotalZero: true, total: 0 },
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
],
|
||||
};
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
filter.hide = { $ne: true };
|
||||
|
||||
@@ -106,10 +106,17 @@
|
||||
/>
|
||||
<smart-switch
|
||||
label="Ignore damage"
|
||||
class="mr-4"
|
||||
:value="model.healthBarNoDamage"
|
||||
:error-messages="errors.healthBarNoDamage"
|
||||
@change="change('healthBarNoDamage', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Prevent damage overflow"
|
||||
:value="model.healthBarNoDamageOverflow"
|
||||
:error-messages="errors.healthBarNoDamageOverflow"
|
||||
@change="change('healthBarNoDamageOverflow', ...arguments)"
|
||||
/>
|
||||
</v-layout>
|
||||
<v-layout wrap>
|
||||
<text-field
|
||||
@@ -125,14 +132,20 @@
|
||||
/>
|
||||
<smart-switch
|
||||
label="Ignore healing"
|
||||
class="mr-4"
|
||||
:value="model.healthBarNoHealing"
|
||||
:error-messages="errors.healthBarNoHealing"
|
||||
@change="change('healthBarNoHealing', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Prevent healing overflow"
|
||||
:value="model.healthBarNoHealingOverflow"
|
||||
:error-messages="errors.healthBarNoHealingOverflow"
|
||||
@change="change('healthBarNoHealingOverflow', ...arguments)"
|
||||
/>
|
||||
</v-layout>
|
||||
</form-section>
|
||||
</v-expand-transition>
|
||||
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
@@ -151,26 +164,74 @@
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<div class="layout column align-center">
|
||||
<smart-switch
|
||||
v-if="model.attributeType !== 'hitDice'"
|
||||
label="Allow decimal values"
|
||||
class="no-flex"
|
||||
:value="model.decimal"
|
||||
:error-messages="errors.decimal"
|
||||
@change="change('decimal', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Can be damaged into negative values"
|
||||
:value="model.ignoreLowerLimit"
|
||||
:error-messages="errors.ignoreLowerLimit"
|
||||
@change="change('ignoreLowerLimit', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Can be incremented above total"
|
||||
:value="model.ignoreUpperLimit"
|
||||
:error-messages="errors.ignoreUpperLimit"
|
||||
@change="change('ignoreUpperLimit', ...arguments)"
|
||||
/>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
v-if="model.attributeType !== 'hitDice'"
|
||||
label="Allow decimal values"
|
||||
class="mx-4"
|
||||
:value="model.decimal"
|
||||
:error-messages="errors.decimal"
|
||||
@change="change('decimal', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Can be damaged into negative values"
|
||||
class="mx-4"
|
||||
:value="model.ignoreLowerLimit"
|
||||
:error-messages="errors.ignoreLowerLimit"
|
||||
@change="change('ignoreLowerLimit', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Can be incremented above total"
|
||||
class="mx-4"
|
||||
:value="model.ignoreUpperLimit"
|
||||
:error-messages="errors.ignoreUpperLimit"
|
||||
@change="change('ignoreUpperLimit', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Hide when total is zero"
|
||||
class="mx-4"
|
||||
:value="model.hideWhenTotalZero"
|
||||
:error-messages="errors.hideWhenTotalZero"
|
||||
@change="change('hideWhenTotalZero', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Hide when value is zero"
|
||||
class="mx-4"
|
||||
:value="model.hideWhenValueZero"
|
||||
:error-messages="errors.hideWhenValueZero"
|
||||
@change="change('hideWhenValueZero', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div
|
||||
class="layout justify-center"
|
||||
style="align-self: stretch;"
|
||||
|
||||
@@ -13,6 +13,16 @@
|
||||
:value="!!model.public + ''"
|
||||
@change="(value, ack) => setSheetPublic({value, ack})"
|
||||
/>
|
||||
<smart-select
|
||||
v-if="docRef.collection === 'libraries'"
|
||||
label="Who can copy from this library"
|
||||
:items="[
|
||||
{text: 'Only people with edit permission', value: 'false'},
|
||||
{text: 'Anyone with read permission', value: 'true'}
|
||||
]"
|
||||
:value="!!model.readersCanCopy + ''"
|
||||
@change="(value, ack) => setReadersCanCopy({value, ack})"
|
||||
/>
|
||||
<text-field
|
||||
v-if="model.public && docRef.collection === 'libraries'"
|
||||
readonly
|
||||
@@ -30,6 +40,7 @@
|
||||
@change="(value, ack) => getUser({value, ack})"
|
||||
/>
|
||||
<v-btn
|
||||
class="ml-2 mt-2"
|
||||
:disabled="userFoundState !== 'found'"
|
||||
@click="updateSharing(userId, 'reader')"
|
||||
>
|
||||
@@ -126,6 +137,7 @@
|
||||
<script lang="js">
|
||||
import {
|
||||
setPublic,
|
||||
setReadersCanCopy,
|
||||
updateUserSharePermissions
|
||||
} from '/imports/api/sharing/sharing.js';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
@@ -157,6 +169,14 @@ export default {
|
||||
ack(error && error.reason || error);
|
||||
});
|
||||
},
|
||||
setReadersCanCopy({ value, ack }) {
|
||||
setReadersCanCopy.call({
|
||||
docRef: this.docRef,
|
||||
readersCanCopy: value === 'true',
|
||||
}, (error) => {
|
||||
ack(error && error.reason || error);
|
||||
});
|
||||
},
|
||||
getUser({ value, ack }) {
|
||||
this.userSearched = value;
|
||||
if (!value) {
|
||||
|
||||
1013
app/package-lock.json
generated
1013
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dicecloud",
|
||||
"version": "2.0.38",
|
||||
"version": "2.0.43",
|
||||
"description": "Unofficial Online Realtime D&D 5e App",
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
@@ -19,57 +19,57 @@
|
||||
"npm": "6.13.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@babel/runtime": "^7.19.4",
|
||||
"@chenfengyuan/vue-countdown": "^1.1.5",
|
||||
"@tozd/vue-observer-utils": "^0.5.0",
|
||||
"animejs": "^2.2.0",
|
||||
"aws-sdk": "^2.1148.0",
|
||||
"bcrypt": "^5.0.0",
|
||||
"aws-sdk": "^2.1234.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
"chroma-js": "^2.4.2",
|
||||
"core-js": "^2.6.11",
|
||||
"css-box-shadow": "^1.0.0-3",
|
||||
"date-fns": "^1.30.1",
|
||||
"ddp-rate-limiter-mixin": "^1.1.10",
|
||||
"discord.js": "^12.5.3",
|
||||
"dompurify": "^2.3.8",
|
||||
"dompurify": "^2.4.0",
|
||||
"ignore": "^5.2.0",
|
||||
"ignore-styles": "^5.0.1",
|
||||
"lodash": "^4.17.20",
|
||||
"marked": "^4.0.16",
|
||||
"meteor-node-stubs": "^1.2.3",
|
||||
"marked": "^4.1.1",
|
||||
"meteor-node-stubs": "^1.2.5",
|
||||
"minify-css-string": "^1.0.0",
|
||||
"moo": "^0.5.1",
|
||||
"moo": "^0.5.2",
|
||||
"nearley": "^2.19.1",
|
||||
"ngraph.graph": "^19.1.0",
|
||||
"ngraph.path": "^1.4.0",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"request": "^2.88.2",
|
||||
"sharp": "^0.30.4",
|
||||
"simpl-schema": "^1.12.2",
|
||||
"sharp": "^0.30.7",
|
||||
"simpl-schema": "^1.13.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"speakingurl": "^14.0.1",
|
||||
"styles": "^0.2.1",
|
||||
"underscore": "^1.13.4",
|
||||
"underscore": "^1.13.6",
|
||||
"vue": "2.6.10",
|
||||
"vue-meteor-tracker": "^2.0.0-beta.5",
|
||||
"vue-meteor-tracker": "^2.0.0",
|
||||
"vue-reactive-provide": "^0.3.0",
|
||||
"vue-router": "^3.5.4",
|
||||
"vue-router": "^3.6.5",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuetify": "^2.6.6",
|
||||
"vuetify": "^2.6.11",
|
||||
"vuetify-upload-button": "^2.0.2",
|
||||
"vuex": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.39.0",
|
||||
"@typescript-eslint/parser": "^5.39.0",
|
||||
"@vue/compiler-dom": "^3.2.40",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"@vue/compiler-dom": "^3.2.41",
|
||||
"chai": "^4.3.6",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"eslint-plugin-vuetify": "^1.1.0",
|
||||
"mem": "^6.1.1",
|
||||
"sass": "^1.52.2",
|
||||
"sass": "^1.55.0",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
@@ -124,4 +124,4 @@
|
||||
"vuetify/no-deprecated-classes": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@ Leveling up a class means choosing, or manually adding, class level properties t
|
||||
|
||||
The total level of the class can be accessed in calculations using `classVariableName.level`.
|
||||
|
||||
## Making your own class
|
||||
|
||||
See [Create a Class](/docs/walkthroughs/create-a-class)
|
||||
|
||||
---
|
||||
|
||||
### Name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Skills
|
||||
|
||||
Skills represent things the creature can be proficient in. Skills can have their values or behavior modifier by [effects](/docs/property/efffect), and their proficiencies modified by [proficiencies](/docs/property/proficiency).
|
||||
Skills represent things the creature can be proficient in. Skills can have their values or behavior modifier by [effects](/docs/property/effect), and their proficiencies modified by [proficiencies](/docs/property/proficiency).
|
||||
|
||||
---
|
||||
|
||||
|
||||
47
app/private/docs/walkthroughs/create-a-class.md
Normal file
47
app/private/docs/walkthroughs/create-a-class.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Create a Class
|
||||
|
||||
This is a guide on creating a custom class in a character sheet. If possible, it is always faster to use an existing library that contains the class you want to use. Before continuing, check the #libraries channel of the [official discord](https://discord.gg/qEvdfeB) to see if a library exists with the class you are creating.
|
||||
|
||||
This guide assumes you are using the ruleset provided in the [5e System Reference Document library](/library/qkv8aptJH2fCXARcJ). If you are using a different ruleset for your character, there may be some discrepancies.
|
||||
|
||||
## Adding the class property
|
||||
|
||||
On the build tab of your character, in the card labeled **Slots**, expand the rulset, then click the slot where you would like to place the custom class, if it is your starting class in an SRD character, this would be the Class slot. Be sure to click the name of the slot, not the **+** button.
|
||||
|
||||

|
||||
|
||||
This opens the slot in detail view, showing you how the slot expected to be filled from a library, instead of filling the slot, we will be manually adding a class to the slot that we create ourselves.
|
||||
|
||||
Click the **Edit** button in the top right of the slot detail dialog.
|
||||
|
||||

|
||||
|
||||
Expand the children of the class slot, and click the plus button to add a child property.
|
||||
|
||||

|
||||
|
||||
This brings up the create a property dialog, we are creating a class, so select the class property type.
|
||||
|
||||

|
||||
|
||||
Now that we have selected the class property type, the create tab is selected where we can enter the details of our class, fill in the form and click **Create**.
|
||||
|
||||

|
||||
|
||||
Now that our custom class is created, we can close the class slot dialog.
|
||||
|
||||
On the Build tab, in the card with the title **Level**, you will see your new class, with a button to **Level Up**, clicking the level up button would usually search your libraries for class levels that match the variable name of the class, however, since it's a custom class, it will probably not find any levels.
|
||||
|
||||
Instead, as we did with the class slot, click on the class name to bring up the class detail dialog, click **Edit**, expand children and click the **+** button to add a child to the class. Here we will add all of the things our class gives the character.
|
||||
|
||||
Add an [Effect](/docs/property/effect) which targets `hitPoints` to add the starting hitpoints of the class. Add a [proficiencies](/docs/property/proficiency) for all the skill and saving throw proficiencies the class gives. Add [skills](/docs/property/skill) for all the tool and weapon proficiencies of the class, making sure to set the base proficiency of those skills to proficient. Add any text [features](/docs/property/feature) the class gives you, along with [actions](/docs/property/action) which may be children of those features, or direct children of the class.
|
||||
|
||||
Once you have added Everything the class gives you, it's time to add class levels. As a child of the class, add a [class level](/docs/property/class-level) property. Set the level to 1 and the name and variable name to match the variable name of the class.
|
||||
|
||||
Once the class level is created, open the class level and edit it. Use the **+** button in the children of the class level to add all the properties the class level gives your character.
|
||||
|
||||
Repeat this for every level of the class until your character is at the correct level.
|
||||
|
||||
You can use a separate character with levels in a class that is available in your libraries as an example of what properties you may want to add to your class and class levels.
|
||||
|
||||

|
||||
BIN
app/public/images/docs/walkthroughs/create-a-class-1.png
Normal file
BIN
app/public/images/docs/walkthroughs/create-a-class-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
app/public/images/docs/walkthroughs/create-a-class-2.png
Normal file
BIN
app/public/images/docs/walkthroughs/create-a-class-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
app/public/images/docs/walkthroughs/create-a-class-3.png
Normal file
BIN
app/public/images/docs/walkthroughs/create-a-class-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
app/public/images/docs/walkthroughs/create-a-class-4.png
Normal file
BIN
app/public/images/docs/walkthroughs/create-a-class-4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
app/public/images/docs/walkthroughs/create-a-class-5.png
Normal file
BIN
app/public/images/docs/walkthroughs/create-a-class-5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
app/public/images/docs/walkthroughs/create-a-class-6.png
Normal file
BIN
app/public/images/docs/walkthroughs/create-a-class-6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Reference in New Issue
Block a user