Continued migrating to Simpl-Schema and imports

This commit is contained in:
Stefan Zermatten
2018-10-19 14:01:23 +02:00
parent 989706483a
commit 7cda854d22
31 changed files with 391 additions and 710 deletions

View File

@@ -1,93 +0,0 @@
/*
Router.map(function() {
this.route("vmixCharacter", {
path: "/vmix-character/:_id/",
where: "server",
action: function() {
this.response.setHeader("Content-Type", "application/json");
var query = this.params.query;
var key = query && query.key;
ifKeyValid(key, this.response, "vmixCharacter", () =>
this.response.end(vMixCharacter(this.params._id))
);
},
});
this.route("vmixParty", {
path: "/vmix-party/:_id/",
where: "server",
action: function() {
this.response.setHeader("Content-Type", "application/json");
var query = this.params.query;
var key = query && query.key;
ifKeyValid(key, this.response, "vmixParty", () =>
this.response.end(vMixParty(this.params._id))
);
},
});
this.route("jsonCharacterSheet", {
path: "/character/:_id/json",
where: "server",
action: function() {
this.response.setHeader("Content-Type", "application/json");
var query = this.params.query;
var key = query && query.key;
ifKeyValid(key, this.response, "jsonCharacterSheet", () => {
if (canViewCharacter(this.params._id, userIdFromKey(key))){
this.response.end(JSONExport(this.params._id))
} else {
this.response.writeHead(403, "You do not have permission to view this character");
this.response.end();
}
}
);
},
});
});
var ifKeyValid = function(apiKey, response, method, callback){
if (!apiKey){
response.writeHead(403, "You must use an api key to access this api");
response.end();
} else if (!isKeyValid(apiKey)){
response.writeHead(403, "API key is invalid");
response.end();
} else if (isRateLimited(apiKey, method)){
response.writeHead(429, "Too many requests");
response.end(JSON.stringify({
"timeToReset": rateLimiter.check({apiKey: apiKey, method: method}).timeToReset
}));
} else {
rateLimiter.increment({apiKey: apiKey, method: method})
callback();
}
};
var isKeyValid = function(apiKey){
var user = Meteor.users.findOne({apiKey});
if (!user) return false;
var blackListed = Blacklist.findOne({userId: user._id});
return !blackListed;
};
var userIdFromKey = function(apiKey){
var user = Meteor.users.findOne({apiKey}); // we know user exists from isKeyValid
return user._id;
}
var rateLimiter = new RateLimiter();
rateLimiter.addRule({apiKey: String}, 5, 5000);
rateLimiter.addRule({apiKey: String, method: "vmixCharacter"}, 2, 10000);
rateLimiter.addRule({apiKey: String, method: "vmixParty"}, 2, 10000);
rateLimiter.addRule({apiKey: String, method: "jsonCharacterSheet"}, 5, 5000);
var isRateLimited = function(apiKey, method){
const limited = !rateLimiter.check({apiKey: apiKey, method: method}).allowed
if (limited) {
console.log(`Rate limit hit by API key ${apiKey}`);
return true;
} else {
return false;
}
};
*/

View File

@@ -1,176 +0,0 @@
/*
Router.configure({
loadingTemplate: "loading",
layoutTemplate: "layout",
trackPageView: true,
});
Router.plugin("ensureSignedIn", {
only: [
"profile",
"characterList",
]
});
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
var handleSubError = function(e){
Session.set("error", {reason: e.reason, href: location.href});
Router.go("/error");
};
Router.map(function() {
this.route("/", {
name: "home",
onAfterAction: function() {
document.title = appName;
},
});
this.route("characterList", {
path: "/characterList",
waitOn: function(){
return subsManager.subscribe("characterList");
},
onAfterAction: function() {
document.title = appName + " - Characters";
},
fastRender: true,
});
this.route("characterSheetNaked", {
path: "/character/:_id/",
waitOn: function(){
return [
subsManager.subscribe(
"singleCharacter", this.params._id, {onError: handleSubError}
),
];
},
action: function(){
var _id = this.params._id
var character = Characters.findOne(_id);
var urlName = character && character.urlName;
var path = `\/character\/${_id}\/${urlName || "-"}`;
Router.go(path,{},{replaceState:true});
},
});
this.route("characterSheet", {
path: "/character/:_id/:urlName",
waitOn: function(){
return [
subsManager.subscribe(
"singleCharacter", this.params._id, {onError: handleSubError}
),
];
},
data: function() {
var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data;
},
onAfterAction: function() {
var char = Characters.findOne({_id: this.params._id}, {fields: {name: 1}});
var name = char && char.name;
if (name){
document.title = name;
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/character");
this.next();
},
fastRender: true,
});
this.route("printedCharacterSheet", {
path: "/character/:_id/:urlName/print",
waitOn: function(){
return [
subsManager.subscribe(
"singleCharacter", this.params._id, {onError: handleSubError}
),
];
},
data: function() {
var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data;
},
onAfterAction: function() {
var char = Characters.findOne({_id: this.params._id}, {fields: {name: 1}});
var name = char && char.name;
if (name){
document.title = name + " - Printing";
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/print-character");
this.next();
},
});
this.route("library", {
path: "/library",
waitOn: function(){
return subsManager.subscribe("standardLibraries");
},
onAfterAction: function() {
document.title = appName + " - Library";
},
fastRender: true,
});
this.route("loading", {
path: "/loading"
});
this.route("profile", {
path: "/account",
onAfterAction: function() {
document.title = appName + " Account";
},
});
this.route("/changelog", {
name: "changeLog",
waitOn: function() {
return [
subsManager.subscribe("changeLog"),
]
},
data: {
changeLogs: function() {
return ChangeLogs.find({}, {sort: {version: -1}});
}
},
onAfterAction: function() {
document.title = appName;
},
fastRender: true,
});
this.route("/guide", {
name: "guide",
onAfterAction: function() {
document.title = appName;
},
});
this.route("/error", {
name: "error",
onAfterAction: function() {
document.title = `${appName} - Error`;
},
});
});
*/

View File

@@ -1,3 +1,5 @@
import SimpleSchema from 'simpl-schema';
let Instances = new Mongo.Collection("instances");
let instanceSchema = new SimpleSchema({

View File

@@ -1,3 +1,5 @@
import SimpleSchema from 'simpl-schema';
let Parties = new Mongo.Collection("parties");
let partySchema = new SimpleSchema({
@@ -19,7 +21,7 @@ let partySchema = new SimpleSchema({
},
});
Parties.attachSchema(Schemas.Party);
Parties.attachSchema(partySchema);
Parties.allow({
insert: function(userId, doc) {

View File

@@ -24,7 +24,7 @@ attributeSchema = new SimpleSchema({
},
// Attributes need to store their order to keep the sheet consistent
order: {
type: Number,
type: SimpleSchema.Integer,
index: 1,
},
type: {
@@ -42,20 +42,18 @@ attributeSchema = new SimpleSchema({
},
baseValue: {
type: Number,
decimal: true,
optional: true,
},
value: {
type: Number,
decimal: true,
defaultValue: 0,
},
mod: {
type: Number,
type: SimpleSchema.Integer,
optional: true,
},
adjustment: {
type: Number,
type: SimpleSchema.Integer,
optional: true,
},
// Can the value be decimal?

View File

@@ -0,0 +1,76 @@
import SimpleSchema from "simpl-schema";
let Bundle = new Mongo.Collection("bundle");
let attributeSchema = new SimpleSchema({
name: String,
variableName: String,
baseValue: String,
type: String,
});
let skillSchema = new SimpleSchema({
name: String,
variableName: String,
ability: String,
type: String,
});
let damageMultiplierSchema = new SimpleSchema({
name: String,
variableName: String,
});
let effectSchema = new SimpleSchema({
name: String,
stat: String,
operation: {type: String},
calculation: {type: String, optional: true},
value: {type: Number, optional: true}
});
let itemSchema = new SimpleSchema({
name: String,
plural: {type: String, optional: true,},
description: {type: String, optional: true,},
quantity: {type: SimpleSchema.Integer, min: 0,},
weight: {type: Number, min: 0,},
value: {type: Number, min: 0,},
requiresAttunement: {type: Boolean, optional: true},
settings: {type: Object, optional: true},
"settings.showIncrement": {type: Boolean, optional: true},
});
let containerSchema = new SimpleSchema({
name: String,
isCarried: Boolean,
weight: {type: Number, min: 0},
value: {type: Number, min: 0},
description:{type: String, optional: true},
items: Array,
"items.$": itemSchema,
});
let featureSchema = new SimpleSchema({
name: String,
description: {type: String, optional: true},
uses: {type: String, optional: true},
alwaysEnabled: {type: Boolean, defaultValue: true},
effects: Array,
"effects.$": effectSchema,
});
let bundleSchema = new SimpleSchema({
attributes: Array,
"attributes.$": attributeSchema,
skills: Array,
"skills.$": skillSchema,
damageMultipliers: Array,
"damageMultipliers.$": damageMultiplierSchema,
effects: Array,
"effects.$": effectSchema,
containers: Array,
"containers.$": containerSchema,
});
export default Bundles;

View File

@@ -7,7 +7,7 @@ let Classes = new Mongo.Collection("classes");
classSchema= new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
level: {type: Number},
level: {type: SimpleSchema.Integer},
createdAt: {
type: Date,
autoValue: function() {

View File

@@ -0,0 +1,74 @@
getDefaultCreatureDocs = function(charId, creatureType = "pc"){
let docs = {attributes: [], skills: [], damageMultipliers: [], effects: []};
if (creatureType === "pc"){
const stats = DEFAULT_CHARACTER_STATS;
} else {
throw new Meteor.Error("Not implemented",
"Default stats for non-player characters aren't implemented yet");
}
let order = 0;
const baseParent = {
collection: "Characters",
id: charId,
group: "default",
};
let name, variableName, parent, attribute, skill, ability, dm, type, baseValue;
for (type in stats.attributes){
for (let i in stats.attributes[type]){
attribute = stats.attributes[type][i];
if (_.isString(attribute)){
name = attribute;
variableName = attribute.toLowerCase();
} else {
name = attribute.name;
variableName = attribute.variableName;
}
baseValue = attribute.baseValue;
parent = _.clone(baseParent);
docs.attributes.push({
_id: Random.id,
charId, name, variableName, order, type, parent, baseValue,
});
order++;
}
}
order = 0;
for (type in stats.skills){
for (let i in stats.skills[type]){
skill = stats.skills[type][i];
docs.skills.push({
_id: Random.id,
charId,
type,
order,
name: skill.name,
variableName: skill.variableName,
ability: skill.ability,
parent: _.clone(baseParent),
});
order++;
}
}
for (let i in stats.damageMultipliers){
dm = stats.damageMultipliers[i];
docs.damageMultipliers.push({
_id: Random.id,
charId,
name: dm.name,
variableName: dm.variableName,
parent: _.clone(baseParent),
});
}
for (let i in stats.effects){
eff = stats.effects[i];
docs.effects.push({
_id: Random.id,
charId,
name: eff.name,
stat: eff.stat,
operation: eff.operation,
calculation:eff.calculation,
});
}
return docs;
}

View File

@@ -1,4 +1,5 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import SimpleSchema from 'simpl-schema';
import Effects from "/imports/api/creature/Effects.js"
import deathSaveSchema from "/imports/api/creature/subSchemas/DeathSaves.js"
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js";
@@ -23,9 +24,9 @@ let creatureSchema = new SimpleSchema({
//mechanics
deathSave: {type: deathSaveSchema},
xp: {type: Number, defaultValue: 0},
xp: {type: SimpleSchema.Integer, defaultValue: 0},
weightCarried: {type: Number, defaultValue: 0},
level: {type: Number, defaultValue: 0},
level: {type: SimpleSchema.Integer, defaultValue: 0},
type: {type: String, defaultValue: "pc", allowedValues: ["pc", "npc", "monster"]},
//permissions
@@ -35,7 +36,7 @@ let creatureSchema = new SimpleSchema({
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [], index: 1},
//TODO add per-creature settings
//how many experiences to load at a time in XP table
"settings.experiencesInc": {type: Number, defaultValue: 20},
"settings.experiencesInc": {type: SimpleSchema.Integer, defaultValue: 20},
//slowed down by carrying too much?
"settings.useVariantEncumbrance": {type: Boolean, defaultValue: false},
"settings.useStandardEncumbrance": {type: Boolean, defaultValue: true},

View File

@@ -1,3 +1,5 @@
import SimpleSchema from 'simpl-schema';
DamageMultipliers = new Mongo.Collection("damageMultipliers");
/*
@@ -19,7 +21,6 @@ Schemas.DamageMultiplier = new SimpleSchema({
},
value: {
type: Number,
decimal: true,
defaultValue: 1,
},
enabled: {

View File

@@ -1,3 +1,4 @@
import SimpleSchema from 'simpl-schema';
import {makeChild} from "/imports/api/parenting.js";
Effects = new Mongo.Collection("effects");
@@ -35,7 +36,6 @@ effectSchema = new SimpleSchema({
},
value: {
type: Number,
decimal: true,
optional: true,
},
calculation: {
@@ -56,7 +56,7 @@ effectSchema = new SimpleSchema({
Effects.attachSchema(effectSchema);
Effects.attachBehaviour("softRemovable");
//Effects.attachBehaviour("softRemovable");
makeChild(Effects, ["enabled"]); //children of lots of things
export default Effects;

View File

@@ -1,10 +1,12 @@
import SimpleSchema from 'simpl-schema';
Experiences = new Mongo.Collection("experience");
Schemas.Experience = new SimpleSchema({
let experienceSchema = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false, defaultValue: "New Experience"},
description: {type: String, optional: true, trim: false},
value: {type: Number, defaultValue: 0},
value: {type: SimpleSchema.Integer, defaultValue: 0},
dateAdded: {
type: Date,
autoValue: function() {
@@ -19,9 +21,6 @@ Schemas.Experience = new SimpleSchema({
},
});
Experiences.attachSchema(Schemas.Experience);
Experiences.attachSchema(experienceSchema);
Experiences.attachBehaviour("softRemovable");
Experiences.allow(CHARACTER_SUBSCHEMA_ALLOW);
Experiences.deny(CHARACTER_SUBSCHEMA_DENY);
//Experiences.attachBehaviour("softRemovable");

View File

@@ -1,12 +1,15 @@
Features = new Mongo.Collection("features");
import SimpleSchema from 'simpl-schema';
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js";
import {makeParent} from "/imports/api/parenting.js";
Schemas.Feature = new SimpleSchema({
let Features = new Mongo.Collection("features");
let featureSchema = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
uses: {type: String, optional: true, trim: false},
used: {type: Number, defaultValue: 0},
used: {type: SimpleSchema.Integer, defaultValue: 0},
reset: {
type: String,
allowedValues: ["manual", "longRest", "shortRest"],
@@ -16,95 +19,10 @@ Schemas.Feature = new SimpleSchema({
alwaysEnabled:{type: Boolean, defaultValue: true},
});
Features.attachSchema(Schemas.Feature);
Features.attachSchema(featureSchema);
Features.attachSchema(ColorSchema);
Features.helpers({
usesLeft: function(){
return evaluate(this.charId, this.uses) - this.used;
},
usesValue: function(){
return evaluate(this.charId, this.uses);
},
});
//Features.attachBehaviour("softRemovable");
makeParent(Features, ["name", "enabled"]); //parents of effects and attacks
//give characters default feature of base ability scores of 10
Characters.after.insert(function(userId, char) {
if (Meteor.isServer){
var featureId = Features.insert({
name: "Base Ability Scores",
charId: char._id,
enabled: true,
alwaysEnabled: true,
});
Effects.insert({
stat: "strength",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "dexterity",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "constitution",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "intelligence",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "wisdom",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
Effects.insert({
stat: "charisma",
charId: char._id,
parent: {
id: featureId,
collection: "Features",
},
operation: "base",
value: 10,
enabled: true,
});
}
});
export default Features;

View File

@@ -1,16 +1,17 @@
Notes = new Mongo.Collection("notes");
import SimpleSchema from 'simpl-schema';
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js";
Schemas.Note = new SimpleSchema({
let Notes = new Mongo.Collection("notes");
noteSchema = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
});
Notes.attachSchema(Schemas.Note);
Attributes.attachSchema(ColorSchema);
Notes.attachSchema(noteSchema);
Notes.attachSchema(ColorSchema);
Notes.attachBehaviour("softRemovable");
//Notes.attachBehaviour("softRemovable");
Notes.allow(CHARACTER_SUBSCHEMA_ALLOW);
Notes.deny(CHARACTER_SUBSCHEMA_DENY);
export default Notes;

View File

@@ -1,6 +1,8 @@
import SimpleSchema from 'simpl-schema';
Proficiencies = new Mongo.Collection("proficiencies");
Schemas.Proficiency = new SimpleSchema({
proficiencySchema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
@@ -15,7 +17,6 @@ Schemas.Proficiency = new SimpleSchema({
type: Number,
allowedValues: [0, 0.5, 1, 2],
defaultValue: 1,
decimal: true,
},
type: {
type: String,
@@ -28,10 +29,7 @@ Schemas.Proficiency = new SimpleSchema({
},
});
Proficiencies.attachSchema(Schemas.Proficiency);
Proficiencies.attachSchema(proficiencySchema);
Proficiencies.attachBehaviour("softRemovable");
// Proficiencies.attachBehaviour("softRemovable");
makeChild(Proficiencies, ["enabled"]);
Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW);
Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,12 +1,13 @@
import SimpleSchema from 'simpl-schema';
import {makeChild} from "/imports/api/parenting.js";
Skills = new Mongo.Collection("skills");
let Skills = new Mongo.Collection("skills");
/*
* Skills are anything that results in a modifier to be added to a D20
* Skills usually have an ability score modifier that they use as their basis
*/
skillSchema = new SimpleSchema({
let skillSchema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
@@ -38,20 +39,18 @@ skillSchema = new SimpleSchema({
},
// Skills need to store their order to keep the sheet consistent
order: {
type: Number,
type: SimpleSchema.Integer,
},
baseValue: {
type: Number,
decimal: true,
optional: true,
},
value: {
type: Number,
decimal: true,
defaultValue: 0,
},
advantage: {
type: Number,
type: SimpleSchema.Integer,
optional: true,
allowedValues: [-1, 0, 1],
},
@@ -62,14 +61,14 @@ skillSchema = new SimpleSchema({
proficiency: {
type: Number,
allowedValues: [0, 0.5, 1, 2],
defaultValue: 0,
defaultValue: 0,
},
conditionalBenefits: {
type: Number,
type: SimpleSchema.Integer,
optional: true,
},
fail: {
type: Number,
type: SimpleSchema.Integer,
optional: true,
},
enabled: {
@@ -80,7 +79,7 @@ skillSchema = new SimpleSchema({
Skills.attachSchema(skillSchema);
Skills.attachBehaviour("softRemovable");
//Skills.attachBehaviour("softRemovable");
makeChild(Skills, ["enabled"]); //children of lots of things
export default Skills;

View File

@@ -1,7 +1,9 @@
SpellLists = new Mongo.Collection("spellLists");
import SimpleSchema from 'simpl-schema';
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js";
Schemas.SpellLists = new SimpleSchema({
let SpellLists = new Mongo.Collection("spellLists");
let spellListSchema = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
@@ -11,7 +13,7 @@ Schemas.SpellLists = new SimpleSchema({
"settings.showUnprepared": {type: Boolean, defaultValue: true},
});
SpellLists.attachSchema(Schemas.SpellLists);
SpellLists.attachSchema(spellListSchema);
Attributes.attachSchema(ColorSchema);
SpellLists.helpers({
@@ -27,8 +29,7 @@ SpellLists.helpers({
}
});
SpellLists.attachBehaviour("softRemovable");
//SpellLists.attachBehaviour("softRemovable");
makeParent(SpellLists); //parents of spells
SpellLists.allow(CHARACTER_SUBSCHEMA_ALLOW);
SpellLists.deny(CHARACTER_SUBSCHEMA_DENY);
export default SpellLists;

View File

@@ -1,7 +1,9 @@
Spells = new Mongo.Collection("spells");
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js";
import SimpleSchema from 'simpl-schema';
Schemas.Spell = new SimpleSchema({
let Spells = new Mongo.Collection("spells");
let spellSchema = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
prepared: {
type: String,
@@ -45,7 +47,7 @@ Schemas.Spell = new SimpleSchema({
defaultValue: false,
},
level: {
type: Number,
type: SimpleSchema.Integer,
defaultValue: 1,
},
school: {
@@ -55,10 +57,10 @@ Schemas.Spell = new SimpleSchema({
},
});
Spells.attachSchema(Schemas.Spell);
Spells.attachSchema(spellSchema);
Attributes.attachSchema(ColorSchema);
Spells.attachBehaviour("softRemovable");
//Spells.attachBehaviour("softRemovable");
makeChild(Spells); //children of spell lists
makeParent(Spells, ["name", "enabled"]); //parents of attacks
@@ -78,12 +80,6 @@ Spells.after.update(function (userId, spell, fieldNames) {
}
});
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
var checkMovePermission = function(spellId, parent, destinationOnly) {
var spell = Spells.findOne(spellId);
if (!spell)
@@ -247,3 +243,5 @@ Meteor.methods({
copySpell(spellId, "Characters", charId);
},
});
export default Spells;

View File

@@ -1,177 +0,0 @@
DEFAULT_CHARACTER_STATS = {
"attributes": {
"ability": [
"Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"
],
"stat": [
"Speed",
{"name": "Armor Class", "variableName": "armor", "baseValue": 10},
],
"hitDice": [
{"name": "d6 Hit Dice", "variableName": "d6HitDice"},
{"name": "d8 Hit Dice", "variableName": "d8HitDice"},
{"name": "d10 Hit Dice", "variableName": "d10HitDice"},
{"name": "d12 Hit Dice", "variableName": "d12HitDice"},
],
"healthBar": [
{"name": "Hit Points", "variableName": "hitPoints"},
{"name": "Temporary Hit Points", "variableName": "tempHitPoints"},
],
"resource": [
"Ki", "Rages",
{"name": "Sourcery Points", "variableName": "sorceryPoints"},
{"name": "Superiority Dice", "variableName": "superiorityDice"},
{"name": "Expertise Dice", "variableName": "expertiseDice"},
],
"spellSlot": [
{"name": "Level 1 Spell Slots", "variableName": "level1SpellSlots"},
{"name": "Level 2 Spell Slots", "variableName": "level2SpellSlots"},
{"name": "Level 3 Spell Slots", "variableName": "level3SpellSlots"},
{"name": "Level 4 Spell Slots", "variableName": "level4SpellSlots"},
{"name": "Level 5 Spell Slots", "variableName": "level5SpellSlots"},
{"name": "Level 6 Spell Slots", "variableName": "level6SpellSlots"},
{"name": "Level 7 Spell Slots", "variableName": "level7SpellSlots"},
{"name": "Level 8 Spell Slots", "variableName": "level8SpellSlots"},
{"name": "Level 9 Spell Slots", "variableName": "level9SpellSlots"},
],
"utility": [
{"name": "Carry Capacity Multiplier", "variableName": "carryMultiplier", "baseValue": 1},
{"name": "Rage Damage", "variableName": "rageDamage"},
],
},
"skills": {
"skill": [
{"name": "Acrobatics", "variableName": "acrobatics", "ability": "dexterity"},
{"name": "Animal Handling", "variableName": "animalHandling", "ability": "wisdom"},
{"name": "Arcana", "variableName": "arcana", "ability": "intelligence"},
{"name": "Athletics", "variableName": "athletics", "ability": "strength"},
{"name": "Deception", "variableName": "deception", "ability": "charisma"},
{"name": "History", "variableName": "history", "ability": "intelligence"},
{"name": "Insight", "variableName": "insight", "ability": "wisdom"},
{"name": "Intimidation", "variableName": "intimidation", "ability": "charisma"},
{"name": "Investigation", "variableName": "investigation", "ability": "intelligence"},
{"name": "Medicine", "variableName": "medicine", "ability": "wisdom"},
{"name": "Nature", "variableName": "nature", "ability": "intelligence"},
{"name": "Perception", "variableName": "perception", "ability": "wisdom"},
{"name": "Performance", "variableName": "performance", "ability": "charisma"},
{"name": "Persuasion", "variableName": "persuasion", "ability": "charisma"},
{"name": "Religion", "variableName": "religion", "ability": "intelligence"},
{"name": "Sleight of Hand", "variableName": "sleightOfHand", "ability": "dexterity"},
{"name": "Stealth", "variableName": "stealth", "ability": "dexterity"},
{"name": "Survival", "variableName": "survival", "ability": "wisdom"},
],
"save": [
{"name": "Strength Save", "variableName": "strengthSave", "ability": "strength"},
{"name": "Dexterity Save", "variableName": "dexteritySave", "ability": "dexterity"},
{"name": "Constitution Save", "variableName": "constitutionSave", "ability": "constitution"},
{"name": "Intelligence Save", "variableName": "intelligenceSave", "ability": "intelligence"},
{"name": "Wisdom Save", "variableName": "wisdomSave", "ability": "wisdom"},
{"name": "Charisma Save", "variableName": "charismaSave", "ability": "charisma"},
],
"stat": [
{"name": "Proficiency Bonus", "variableName": "proficiencyBonus"},
{"name": "initiative", "variableName": "initiative"},
],
},
"damageMultipliers": [
{"name": "Acid Multiplier", "variableName":"acidMultiplier"},
{"name": "Bludgeoning Multiplier", "variableName":"bludgeoningMultiplier"},
{"name": "Cold Multiplier", "variableName":"coldMultiplier"},
{"name": "Fire Multiplier", "variableName":"fireMultiplier"},
{"name": "Force Multiplier", "variableName":"forceMultiplier"},
{"name": "Lightning Multiplier", "variableName":"lightningMultiplier"},
{"name": "Necrotic Multiplier", "variableName":"necroticMultiplier"},
{"name": "Piercing Multiplier", "variableName":"piercingMultiplier"},
{"name": "Poison Multiplier", "variableName":"poisonMultiplier"},
{"name": "Psychic Multiplier", "variableName":"psychicMultiplier"},
{"name": "Radiant Multiplier", "variableName":"radiantMultiplier"},
{"name": "Slashing Multiplier", "variableName":"slashingMultiplier"},
{"name": "Thunder Multiplier", "variableName":"thunderMultiplier"},
],
"effects": [
{
"name": "Proficiency bonus by level",
"stat": "proficiencyBonus",
"operation": "add",
"calculation": "floor(level / 4 + 1.75)",
},
]
}
getDefaultCreatureDocs = function(charId, creatureType = "pc"){
let docs = {attributes: [], skills: [], damageMultipliers: [], effects: []};
if (creatureType === "pc"){
const stats = DEFAULT_CHARACTER_STATS;
} else {
throw new Meteor.Error("Not implemented",
"Default stats for non-player characters aren't implemented yet");
}
let order = 0;
const baseParent = {
collection: "Characters",
id: charId,
group: "default",
};
let name, variableName, parent, attribute, skill, ability, dm, type, baseValue;
for (type in stats.attributes){
for (let i in stats.attributes[type]){
attribute = stats.attributes[type][i];
if (_.isString(attribute)){
name = attribute;
variableName = attribute.toLowerCase();
} else {
name = attribute.name;
variableName = attribute.variableName;
}
baseValue = attribute.baseValue;
parent = _.clone(baseParent);
docs.attributes.push({
_id: Random.id,
charId, name, variableName, order, type, parent, baseValue,
});
order++;
}
}
order = 0;
for (type in stats.skills){
for (let i in stats.skills[type]){
skill = stats.skills[type][i];
docs.skills.push({
_id: Random.id,
charId,
type,
order,
name: skill.name,
variableName: skill.variableName,
ability: skill.ability,
parent: _.clone(baseParent),
});
order++;
}
}
for (let i in stats.damageMultipliers){
dm = stats.damageMultipliers[i];
docs.damageMultipliers.push({
_id: Random.id,
charId,
name: dm.name,
variableName: dm.variableName,
parent: _.clone(baseParent),
});
}
for (let i in stats.effects){
eff = stats.effects[i];
docs.effects.push({
_id: Random.id,
charId,
name: eff.name,
stat: eff.stat,
operation: eff.operation,
calculation:eff.calculation,
});
}
return docs;
}

View File

@@ -1,4 +1,5 @@
import SimpleSchema from 'simpl-schema';
const ColorSchema = new SimpleSchema({
color: {
type: String,

View File

@@ -1,13 +1,14 @@
import SimpleSchema from 'simpl-schema';
const DeathSavesSchema = new SimpleSchema({
pass: {
type: Number,
type: SimpleSchema.Integer,
min: 0,
max: 3,
defaultValue: 0,
},
fail: {
type: Number,
type: SimpleSchema.Integer,
min: 0,
max: 3,
defaultValue: 0,

View File

@@ -1,12 +1,15 @@
//set up the collection for containers
Containers = new Mongo.Collection("containers");
import SimpleSchema from 'simpl-schema';
import {makeParent} from "/imports/api/parenting.js";
Schemas.Container = new SimpleSchema({
//set up the collection for containers
let Containers = new Mongo.Collection("containers");
let containerSchema = new SimpleSchema({
name: {type: String, optional: true, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
isCarried: {type: Boolean},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
weight: {type: Number, min: 0, defaultValue: 0},
value: {type: Number, min: 0, defaultValue: 0},
description:{type: String, optional: true, trim: false},
color: {
type: String,
@@ -15,7 +18,7 @@ Schemas.Container = new SimpleSchema({
},
});
Containers.attachSchema(Schemas.Container);
Containers.attachSchema(containerSchema);
Containers.helpers({
contentsValue: function(){
@@ -50,7 +53,7 @@ Containers.helpers({
},
});
Containers.attachBehaviour("softRemovable");
// Containers.attachBehaviour("softRemovable");
makeParent(Containers); //parents of items
Containers.allow(CHARACTER_SUBSCHEMA_ALLOW);
export default Containers;

View File

@@ -1,3 +1,6 @@
import SimpleSchema from 'simpl-schema';
import {makeParent, makeChild} from "/imports/api/parenting.js";
Items = new Mongo.Collection("items");
Schemas.Item = new SimpleSchema({
@@ -5,9 +8,9 @@ Schemas.Item = new SimpleSchema({
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
quantity: {type: Number, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
quantity: {type: SimpleSchema.Integer, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0},
value: {type: Number, min: 0, defaultValue: 0},
enabled: {type: Boolean, defaultValue: false},
requiresAttunement: {type: Boolean, defaultValue: false},
"settings.showIncrement": {type: Boolean, defaultValue: false},
@@ -200,11 +203,11 @@ Items.before.update(function(userId, doc, fieldNames, modifier, options){
}
});
Items.attachBehaviour("softRemovable");
// Items.attachBehaviour("softRemovable");
makeChild(Items); //children of containers
makeParent(Items, ["name", "enabled"]); //parents of effects and attacks
Items.allow(CHARACTER_SUBSCHEMA_ALLOW);
//Items.allow(CHARACTER_SUBSCHEMA_ALLOW);
//give characters default items
Characters.after.insert(function(userId, char) {

View File

@@ -5,9 +5,9 @@ Schemas.LibraryItems = new SimpleSchema({
name: {type: String, defaultValue: "New Item", trim: false},
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},
quantity: {type: Number, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
quantity: {type: SimpleSchema.Integer, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0},
value: {type: Number, min: 0, defaultValue: 0},
requiresAttunement: {type: Boolean, defaultValue: false},
library: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},

View File

@@ -37,7 +37,7 @@ Schemas.LibrarySpells = new SimpleSchema({
defaultValue: false,
},
level: {
type: Number,
type: SimpleSchema.Integer,
defaultValue: 1,
},
school: {

View File

@@ -1,4 +1,6 @@
Schemas.LibraryAttacks = new SimpleSchema({
import SimpleSchema from 'simpl-schema';
libraryAttacksSchema = new SimpleSchema({
name: {
type: String,
defaultValue: "New Attack",
@@ -39,3 +41,5 @@ Schemas.LibraryAttacks = new SimpleSchema({
defaultValue: "slashing",
},
});
export default libraryAttacksSchema;

View File

@@ -1,4 +1,6 @@
Schemas.LibraryEffects = new SimpleSchema({
import SimpleSchema from 'simpl-schema';
libraryEffectsSchema = new SimpleSchema({
name: {
type: String,
optional: true, //TODO make necessary if there is no owner
@@ -24,7 +26,6 @@ Schemas.LibraryEffects = new SimpleSchema({
// Effects either have a value OR a calculation
value: {
type: Number,
decimal: true,
optional: true,
},
calculation: {
@@ -38,3 +39,5 @@ Schemas.LibraryEffects = new SimpleSchema({
optional: true,
},
});
export default libraryEffectsSchema;

View File

@@ -22,7 +22,7 @@ Schemas.Report = new SimpleSchema({
},
//the immediate impact of doing this action (eg. -1 rages)
severity: {
type: Number,
type: SimpleSchema.Integer,
defaultValue: 5,
min: 1,
max: 10,

0
app/server/main.js Normal file
View File

5
app/sharedMain.js Normal file
View File

@@ -0,0 +1,5 @@
import SimpleSchema from 'simpl-schema';
if (Meteor.isDevelopment){
SimpleSchema.debug = true
}

View File

@@ -1,96 +1,135 @@
{
"attributes": {
"ability": [
"Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"
],
"stat": [
"Speed",
{"name": "Armor Class", "variableName": "armor"},
],
"hitDice": [
{"name": "d6 Hit Dice", "variableName": "d6HitDice"},
{"name": "d8 Hit Dice", "variableName": "d8HitDice"},
{"name": "d10 Hit Dice", "variableName": "d10HitDice"},
{"name": "d12 Hit Dice", "variableName": "d12HitDice"},
],
"healthBar": [
{"name": "Hit Points", "variableName": "hitPoints"},
{"name": "Temporary Hit Points", "variableName": "tempHitPoints"},
],
"resource": [
"Ki", "Rages",
{"name": "Sourcery Points", "variableName": "sorceryPoints"},
{"name": "Superiority Dice", "variableName": "superiorityDice"},
{"name": "Expertise Dice", "variableName": "expertiseDice"},
],
"spellSlot": [
{"name": "Level 1 Spell Slots", "variableName": "level1SpellSlots"},
{"name": "Level 2 Spell Slots", "variableName": "level2SpellSlots"},
{"name": "Level 3 Spell Slots", "variableName": "level3SpellSlots"},
{"name": "Level 4 Spell Slots", "variableName": "level4SpellSlots"},
{"name": "Level 5 Spell Slots", "variableName": "level5SpellSlots"},
{"name": "Level 6 Spell Slots", "variableName": "level6SpellSlots"},
{"name": "Level 7 Spell Slots", "variableName": "level7SpellSlots"},
{"name": "Level 8 Spell Slots", "variableName": "level8SpellSlots"},
{"name": "Level 9 Spell Slots", "variableName": "level9SpellSlots"},
],
"utility": [
{"name": "Carry Capacity Multiplier", "variableName": "carryMultiplier"},
{"name": "Rage Damage", "variableName": "rageDamage"},
],
},
"attributes": [
{"name": "Strength", "variableName": "strength", "baseValue": 10, "type": "ability"},
{"name": "Dexterity", "variableName": "dexterity", "baseValue": 10, "type": "ability"},
{"name": "Constitution", "variableName": "constitution", "baseValue": 10, "type": "ability"},
{"name": "Intelligence", "variableName": "intelligence", "baseValue": 10, "type": "ability"},
{"name": "Wisdom", "variableName": "wisdom", "baseValue": 10, "type": "ability"},
{"name": "Charisma", "variableName": "charisma", "baseValue": 10, "type": "ability"},
"skills": {
"skill": [
{"name": "Acrobatics", "variableName": "acrobatics", "ability": "dexterity"},
{"name": "Animal Handling", "variableName": "animalHandling", "ability": "wisdom"},
{"name": "Arcana", "variableName": "arcana", "ability": "intelligence"},
{"name": "Athletics", "variableName": "athletics", "ability": "strength"},
{"name": "Deception", "variableName": "deception", "ability": "charisma"},
{"name": "History", "variableName": "history", "ability": "intelligence"},
{"name": "Insight", "variableName": "insight", "ability": "wisdom"},
{"name": "Intimidation", "variableName": "intimidation", "ability": "charisma"},
{"name": "Investigation", "variableName": "investigation", "ability": "intelligence"},
{"name": "Medicine", "variableName": "medicine", "ability": "wisdom"},
{"name": "Nature", "variableName": "nature", "ability": "intelligence"},
{"name": "Perception", "variableName": "perception", "ability": "wisdom"},
{"name": "Performance", "variableName": "performance", "ability": "charisma"},
{"name": "Persuasion", "variableName": "persuasion", "ability": "charisma"},
{"name": "Religion", "variableName": "religion", "ability": "intelligence"},
{"name": "Sleight of Hand", "variableName": "sleightOfHand", "ability": "dexterity"},
{"name": "Stealth", "variableName": "stealth", "ability": "dexterity"},
{"name": "Survival", "variableName": "survival", "ability": "wisdom"},
],
"save": [
{"name": "Strength Save", "variableName": "strengthSave", "ability": "strength"},
{"name": "Dexterity Save", "variableName": "dexteritySave", "ability": "dexterity"},
{"name": "Constitution Save", "variableName": "constitutionSave", "ability": "constitution"},
{"name": "Intelligence Save", "variableName": "intelligenceSave", "ability": "intelligence"},
{"name": "Wisdom Save", "variableName": "wisdomSave", "ability": "wisdom"},
{"name": "Charisma Save", "variableName": "charismaSave", "ability": "charisma"},
],
"stat": [
{"name": "Proficiency Bonus", "variableName": "proficiencyBonus"},
{"name": "initiative", "variableName": "initiative"},
],
"utility": [
{"name": "Dexterity Armor", "variableName": "dexterityArmor", "ability": "dexterity"},
],
},
{"name": "Speed", "variableName": "speed", "baseValue": 30, "type": "stat"},
{"name": "Armor Class", "variableName": "armor", "baseValue": 10, "type": "stat"},
{"name": "d6 Hit Dice", "variableName": "d6HitDice", "type": "hitDice"},
{"name": "d8 Hit Dice", "variableName": "d8HitDice", "type": "hitDice"},
{"name": "d10 Hit Dice", "variableName": "d10HitDice", "type": "hitDice"},
{"name": "d12 Hit Dice", "variableName": "d12HitDice", "type": "hitDice"},
{"name": "Hit Points", "variableName": "hitPoints", "type": "healthBar"},
{"name": "Temporary Hit Points", "variableName": "tempHitPoints", "type": "healthBar"},
{"name": "Ki", "variableName": "ki", "type": "resource"},
{"name": "Rages", "variableName": "rages", "type": "resource"},
{"name": "Sourcery Points", "variableName": "sorceryPoints", "type": "resource"},
{"name": "Superiority Dice", "variableName": "superiorityDice", "type": "resource"},
{"name": "Expertise Dice", "variableName": "expertiseDice", "type": "resource"},
{"name": "Level 1 Spell Slots", "variableName": "level1SpellSlots", "type": "spellSlot"},
{"name": "Level 2 Spell Slots", "variableName": "level2SpellSlots", "type": "spellSlot"},
{"name": "Level 3 Spell Slots", "variableName": "level3SpellSlots", "type": "spellSlot"},
{"name": "Level 4 Spell Slots", "variableName": "level4SpellSlots", "type": "spellSlot"},
{"name": "Level 5 Spell Slots", "variableName": "level5SpellSlots", "type": "spellSlot"},
{"name": "Level 6 Spell Slots", "variableName": "level6SpellSlots", "type": "spellSlot"},
{"name": "Level 7 Spell Slots", "variableName": "level7SpellSlots", "type": "spellSlot"},
{"name": "Level 8 Spell Slots", "variableName": "level8SpellSlots", "type": "spellSlot"},
{"name": "Level 9 Spell Slots", "variableName": "level9SpellSlots", "type": "spellSlot"},
{"name": "Carry Capacity Multiplier", "variableName": "carryMultiplier", "type": "utility", "baseValue": 1},
{"name": "Rage Damage", "variableName": "rageDamage", "type": "utility"},
],
"skills": [
{"name": "Acrobatics", "variableName": "acrobatics", "ability": "dexterity", "type":"skill"},
{"name": "Animal Handling", "variableName": "animalHandling", "ability": "wisdom", "type":"skill"},
{"name": "Arcana", "variableName": "arcana", "ability": "intelligence", "type":"skill"},
{"name": "Athletics", "variableName": "athletics", "ability": "strength", "type":"skill"},
{"name": "Deception", "variableName": "deception", "ability": "charisma", "type":"skill"},
{"name": "History", "variableName": "history", "ability": "intelligence", "type":"skill"},
{"name": "Insight", "variableName": "insight", "ability": "wisdom", "type":"skill"},
{"name": "Intimidation", "variableName": "intimidation", "ability": "charisma", "type":"skill"},
{"name": "Investigation", "variableName": "investigation", "ability": "intelligence", "type":"skill"},
{"name": "Medicine", "variableName": "medicine", "ability": "wisdom", "type":"skill"},
{"name": "Nature", "variableName": "nature", "ability": "intelligence", "type":"skill"},
{"name": "Perception", "variableName": "perception", "ability": "wisdom", "type":"skill"},
{"name": "Performance", "variableName": "performance", "ability": "charisma", "type":"skill"},
{"name": "Persuasion", "variableName": "persuasion", "ability": "charisma", "type":"skill"},
{"name": "Religion", "variableName": "religion", "ability": "intelligence", "type":"skill"},
{"name": "Sleight of Hand", "variableName": "sleightOfHand", "ability": "dexterity", "type":"skill"},
{"name": "Stealth", "variableName": "stealth", "ability": "dexterity", "type":"skill"},
{"name": "Survival", "variableName": "survival", "ability": "wisdom", "type":"skill"},
{"name": "Strength Save", "variableName": "strengthSave", "ability": "strength", "type":"save"},
{"name": "Dexterity Save", "variableName": "dexteritySave", "ability": "dexterity", "type":"save"},
{"name": "Constitution Save", "variableName": "constitutionSave", "ability": "constitution", "type":"save"},
{"name": "Intelligence Save", "variableName": "intelligenceSave", "ability": "intelligence", "type":"save"},
{"name": "Wisdom Save", "variableName": "wisdomSave", "ability": "wisdom", "type":"save"},
{"name": "Charisma Save", "variableName": "charismaSave", "ability": "charisma", "type":"save"},
{"name": "Proficiency Bonus", "variableName": "proficiencyBonus", "type": "stat"},
{"name": "initiative", "variableName": "initiative", "type": "stat"},
],
"damageMultipliers": [
{"name": "Acid Multiplier", "variableName":"acidMultiplier"},
{"name": "Acid Multiplier", "variableName":"acidMultiplier"},
{"name": "Bludgeoning Multiplier", "variableName":"bludgeoningMultiplier"},
{"name": "Cold Multiplier", "variableName":"coldMultiplier"},
{"name": "Fire Multiplier", "variableName":"fireMultiplier"},
{"name": "Force Multiplier", "variableName":"forceMultiplier"},
{"name": "Lightning Multiplier", "variableName":"lightningMultiplier"},
{"name": "Necrotic Multiplier", "variableName":"necroticMultiplier"},
{"name": "Piercing Multiplier", "variableName":"piercingMultiplier"},
{"name": "Poison Multiplier", "variableName":"poisonMultiplier"},
{"name": "Psychic Multiplier", "variableName":"psychicMultiplier"},
{"name": "Radiant Multiplier", "variableName":"radiantMultiplier"},
{"name": "Slashing Multiplier", "variableName":"slashingMultiplier"},
{"name": "Thunder Multiplier", "variableName":"thunderMultiplier"},
]
{"name": "Cold Multiplier", "variableName":"coldMultiplier"},
{"name": "Fire Multiplier", "variableName":"fireMultiplier"},
{"name": "Force Multiplier", "variableName":"forceMultiplier"},
{"name": "Lightning Multiplier", "variableName":"lightningMultiplier"},
{"name": "Necrotic Multiplier", "variableName":"necroticMultiplier"},
{"name": "Piercing Multiplier", "variableName":"piercingMultiplier"},
{"name": "Poison Multiplier", "variableName":"poisonMultiplier"},
{"name": "Psychic Multiplier", "variableName":"psychicMultiplier"},
{"name": "Radiant Multiplier", "variableName":"radiantMultiplier"},
{"name": "Slashing Multiplier", "variableName":"slashingMultiplier"},
{"name": "Thunder Multiplier", "variableName":"thunderMultiplier"},
],
"effects": [
{
"name": "Proficiency bonus by level",
"stat": "proficiencyBonus",
"operation": "add",
"calculation": "floor(level / 4 + 1.75)",
},
],
"containers": [
{
"name": "Coin Pouch",
"isCarried": true,
"description": "A sturdy pouch for coins",
"items": [
{
"name": "Gold piece",
"plural": "Gold pieces",
"quantity": 0,
"weight": 0.02,
"value": 1,
"settings": {
"showIncrement": true,
},
}, {
"name": "Silver piece",
"plural": "Silver pieces",
"quantity": 0,
"weight": 0.02,
"value": 0.1,
"settings": {
"showIncrement": true,
},
}, {
"name": "Copper piece",
"plural": "Copper pieces",
"quantity": 0,
"weight": 0.02,
"value": 0.01,
"settings": {
"showIncrement": true,
},
},
],
},
],
}