From 798cf3edd767d9f0b17483de66f265bc3f6fa1de Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 19 Dec 2018 14:15:56 +0200 Subject: [PATCH] Characters now insert with intelligent defaults based on the character wizard --- app/imports/api/creature/Creatures.js | 10 +- ...TER_STATS.js => DEFAULT_CHARACTER_DOCS.js} | 7 +- .../api/creature/getDefaultCharacterDocs.js | 173 ++++++++++++++++++ .../api/creature/getDefaultCreatureDocs.js | 104 ----------- app/imports/api/creature/insertCreature.js | 44 +++-- .../creature/properties/DamageMultipliers.js | 8 +- app/imports/api/inventory/Items.js | 1 + .../ui/character/CharacterCreationDialog.vue | 2 +- app/imports/ui/pages/CharacterList.vue | 6 +- 9 files changed, 221 insertions(+), 134 deletions(-) rename app/imports/api/creature/{DEFAULT_CHARACTER_STATS.js => DEFAULT_CHARACTER_DOCS.js} (97%) create mode 100644 app/imports/api/creature/getDefaultCharacterDocs.js delete mode 100644 app/imports/api/creature/getDefaultCreatureDocs.js diff --git a/app/imports/api/creature/Creatures.js b/app/imports/api/creature/Creatures.js index 212dac2c..aa0fe97c 100644 --- a/app/imports/api/creature/Creatures.js +++ b/app/imports/api/creature/Creatures.js @@ -1,9 +1,10 @@ -import { ValidatedMethod } from 'meteor/mdg:validated-method'; import SimpleSchema from 'simpl-schema'; import Effects from "/imports/api/creature/properties/Effects.js" import deathSaveSchema from "/imports/api/creature/subSchemas/DeathSavesSchema.js" import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js"; -import getDefaultCreatureDocs from "/imports/api/creature/getDefaultCreatureDocs.js"; + +//Methods +import '/imports/api/creature/insertCreature.js'; //set up the collection for creatures Creatures = new Mongo.Collection("creatures"); @@ -24,20 +25,19 @@ let creatureSchema = new SimpleSchema({ backstory: {type: String, defaultValue: "", trim: false, optional: true}, //mechanics - deathSave: {type: deathSaveSchema}, + deathSave: {type: deathSaveSchema, defaultValue: {}}, xp: {type: SimpleSchema.Integer, defaultValue: 0}, weightCarried: {type: Number, defaultValue: 0}, level: {type: SimpleSchema.Integer, defaultValue: 0}, type: {type: String, defaultValue: "pc", allowedValues: ["pc", "npc", "monster"]}, //permissions - party: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true}, owner: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, readers: {type: Array, defaultValue: [], index: 1}, "readers.$": {type: String, regEx: SimpleSchema.RegEx.Id}, writers: {type: Array, defaultValue: [], index: 1}, "writers.$": {type: String, regEx: SimpleSchema.RegEx.Id}, - settings: {type: Object}, + settings: {type: Object, defaultValue: {}}, //how many experiences to load at a time in XP table "settings.experiencesInc": {type: SimpleSchema.Integer, defaultValue: 20}, //slowed down by carrying too much? diff --git a/app/imports/api/creature/DEFAULT_CHARACTER_STATS.js b/app/imports/api/creature/DEFAULT_CHARACTER_DOCS.js similarity index 97% rename from app/imports/api/creature/DEFAULT_CHARACTER_STATS.js rename to app/imports/api/creature/DEFAULT_CHARACTER_DOCS.js index 4e30e0d8..75c68a2d 100644 --- a/app/imports/api/creature/DEFAULT_CHARACTER_STATS.js +++ b/app/imports/api/creature/DEFAULT_CHARACTER_DOCS.js @@ -1,4 +1,7 @@ -export default { +// Don't just export a constant, because deep nested objects could be changed +// by code that requires it. Exporting a function that returns the newly created +// object is a little safer. +export default () => ({ "attributes": [ {"name": "Strength", "variableName": "strength", "baseValue": 10, "type": "ability"}, {"name": "Dexterity", "variableName": "dexterity", "baseValue": 10, "type": "ability"}, @@ -132,4 +135,4 @@ export default { ], }, ], -} +}); diff --git a/app/imports/api/creature/getDefaultCharacterDocs.js b/app/imports/api/creature/getDefaultCharacterDocs.js new file mode 100644 index 00000000..6dfc1cfd --- /dev/null +++ b/app/imports/api/creature/getDefaultCharacterDocs.js @@ -0,0 +1,173 @@ +import DEFAULT_CHARACTER_DOCS from '/imports/api/creature/DEFAULT_CHARACTER_DOCS.js'; + +const setParent = function(charId){ + let parent = { + collection: "Creatures", + id: charId, + group: "default", + }; + return doc => { + doc.parent = parent; + doc.charId = charId; + }; +}; + +const getRacialBonusEffect = function(charId, attribute, bonus){ + return { + name: "Race Bonus", + stat: attribute, + operation: "add", + value: bonus, + parent: { + collection: "Creatures", + id: charId, + group: "racial", + }, + charId: charId, + }; +}; + +const giveDocsOrder = function(docArray){ + for (i in docArray){ + docArray[i].order = +i; + } +}; + +const getDefaultCharacterDocs = function(charId, { + // Character form data + baseStrength = 10, + baseDexterity = 10, + baseConstitution = 10, + baseIntelligence = 10, + baseWisdom = 10, + baseCharisma = 10, + strengthBonus = 0, + dexterityBonus = 0, + constitutionBonus = 0, + intelligenceBonus = 0, + wisdomBonus = 0, + charismaBonus = 0, + hitDice = "d8", + cls = "Class", + level = 1, +}){ + let docs = DEFAULT_CHARACTER_DOCS(); + + // Setup the base ability scores + docs.attributes[0].baseValue = baseStrength; + docs.attributes[1].baseValue = baseDexterity; + docs.attributes[2].baseValue = baseConstitution; + docs.attributes[3].baseValue = baseIntelligence; + docs.attributes[4].baseValue = baseWisdom; + docs.attributes[5].baseValue = baseCharisma; + + // Set up racial bonuses + if (strengthBonus) { + docs.effects.push( + getRacialBonusEffect(charId, 'strength', strengthBonus) + ); + } + if (dexterityBonus) { + docs.effects.push( + getRacialBonusEffect(charId, 'dexterity', dexterityBonus) + ); + } + if (constitutionBonus) { + docs.effects.push( + getRacialBonusEffect(charId, 'constitution', constitutionBonus) + ); + } + if (intelligenceBonus) { + docs.effects.push( + getRacialBonusEffect(charId, 'intelligence', intelligenceBonus) + ); + } + if (wisdomBonus) { + docs.effects.push( + getRacialBonusEffect(charId, 'wisdom', wisdomBonus) + ); + } + if (charismaBonus) { + docs.effects.push( + getRacialBonusEffect(charId, 'charisma', charismaBonus) + ); + } + + // Set up Class + const strippedCls = cls.replace(/\s+/g, '') + const classId = Random.id(); + docs.classes = [{ + charId, + level, + name: cls, + }]; + + // Setup hit dice + docs.effects.push({ + name: cls, + stat: `${hitDice}HitDice`, + operation: "add", + calculation: `${strippedCls}Level`, + parent: { + collection: "Classes", + id: classId, + }, + charId: charId, + }); + + // Setup health for all class levels + let healthPerLevel = 4; + if (hitDice == "d6"){ + healthPerLevel = 4; + } else if (hitDice == "d8"){ + healthPerLevel = 5; + } else if (hitDice == "d10"){ + healthPerLevel = 6; + } else if (hitDice == "d12"){ + healthPerLevel = 7; + } + docs.effects.push({ + name: cls, + stat: `${hitDice}HitDice`, + operation: "add", + calculation: `${healthPerLevel - 2} + ${healthPerLevel} * ${strippedCls}Level`, + parent: { + collection: "Classes", + id: classId, + }, + charId: charId, + }); + + // Set the parents for base items + docs.attributes.forEach(setParent(charId)); + docs.skills.forEach(setParent(charId)); + docs.damageMultipliers.forEach(setParent(charId)); + docs.effects.forEach(setParent(charId)); + docs.containers.forEach(setParent(charId)); + + // Set up parenting on items and move them to the top level items object + docs.items = []; + docs.containers.forEach(container => { + container._id = Random.id(); + const parent = { + collection: "Containers", + id: container._id, + }; + container.items.forEach(item => { + item.parent = parent; + item.charId = charId; + }); + // Move the items to the top level array + docs.items.push(...container.items); + delete container.items; + }); + + // Order the docs + for (collection in docs){ + giveDocsOrder(docs[collection]); + } + + return docs +}; + +export default getDefaultCharacterDocs; diff --git a/app/imports/api/creature/getDefaultCreatureDocs.js b/app/imports/api/creature/getDefaultCreatureDocs.js deleted file mode 100644 index 2828d938..00000000 --- a/app/imports/api/creature/getDefaultCreatureDocs.js +++ /dev/null @@ -1,104 +0,0 @@ -import DEFAULT_CHARACTER_STATS from '/imports/api/creature/DEFAULT_CHARACTER_STATS.js'; - -getDefaultCreatureDocs = function(charId, creatureType = "pc"){ - // Setup the docs object which will be returned - let docs = { - attributes: [], - skills: [], - damageMultipliers: [], - effects: [] - }; - - // Get the default character stats - let stats; - if (creatureType === "pc"){ - stats = DEFAULT_CHARACTER_STATS; - } else { - stats = null; - throw new Meteor.Error("Not implemented", - "Default stats for non-player characters aren't implemented yet"); - } - - // Setup the variables we'll need to share - let order = 0; - const baseParent = { - collection: "Characters", - id: charId, - group: "default", - }; - let name, variableName, parent, attribute, skill, dm, type, baseValue; - - // Attributes - 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++; - } - } - - // Skills - 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++; - } - } - - // Damage Multipliers - order = 0; - for (let i in stats.damageMultipliers){ - dm = stats.damageMultipliers[i]; - docs.damageMultipliers.push({ - _id: Random.id, - charId, - order, - name: dm.name, - variableName: dm.variableName, - parent: _.clone(baseParent), - }); - order++; - } - - // Effects - order = 0; - for (let i in stats.effects){ - eff = stats.effects[i]; - docs.effects.push({ - _id: Random.id, - charId, - order, - name: eff.name, - stat: eff.stat, - operation: eff.operation, - calculation:eff.calculation, - }); - order++; - } - return docs; -} - -export default getDefaultCreatureDocs; diff --git a/app/imports/api/creature/insertCreature.js b/app/imports/api/creature/insertCreature.js index 0b1be7a1..05f2e9b3 100644 --- a/app/imports/api/creature/insertCreature.js +++ b/app/imports/api/creature/insertCreature.js @@ -1,35 +1,47 @@ -import getDefaultCreatureDocs from '/imports/api/creature/getDefaultCreatureDocs.js'; +import getDefaultCharacterDocs from '/imports/api/creature/getDefaultCharacterDocs.js'; +import Attributes from '/imports/api/creature/properties/Attributes.js'; +import Skills from '/imports/api/creature/properties/Skills.js'; +import DamageMultipliers from '/imports/api/creature/properties/DamageMultipliers.js'; +import Effects from '/imports/api/creature/properties/Effects.js'; +import Containers from '/imports/api/inventory/Containers.js'; +import Items from '/imports/api/inventory/Items.js'; +import Classes from '/imports/api/creature/properties/Classes.js'; -const addDefaultStats = function(charId){ - const defaultDocs = getDefaultCreatureDocs(charId); - Attributes.rawCollection().insert(getDefa.attributes, {ordered: false}); - Skills.rawCollection().insert(getDefa.skills, {ordered: false}); - DamageMultipliers.rawCollection().insert(getDefa.damageMultipliers, {ordered: false}); +const addDefaultDocs = function(docs){ + Attributes.rawCollection().insert(docs.attributes, {ordered: false}); + Skills.rawCollection().insert(docs.skills, {ordered: false}); + DamageMultipliers.rawCollection().insert(docs.damageMultipliers, {ordered: false}); + Effects.rawCollection().insert(docs.effects, {ordered: false}); + Containers.rawCollection().insert(docs.containers, {ordered: false}); + Items.rawCollection().insert(docs.items, {ordered: false}); + Classes.rawCollection().insert(docs.classes, {ordered: false}); }; const insertCreature = new ValidatedMethod({ name: "Creatures.methods.insertCharacter", // DDP method name - validate: new SimpleSchema({ - name: { - type: String, - optional: true, - }, - }).validator(), + validate: null, - run({name}) { + run(characterFormData) { if (!this.userId) { throw new Meteor.Error("Creatures.methods.insert.denied", "You need to be logged in to insert a creature"); } // Create the creature document - let charId = Creatures.insert({name, owner: this.userId}); + let charId = Creatures.insert({ + name: characterFormData.name, + owner: this.userId, + alignment: characterFormData.alignment, + gender: characterFormData.gender, + race: characterFormData.race, + }); this.unblock(); - //Add all the required attributes to it if (Meteor.isServer){ - addDefaultStats(charId); + //Add all the required attributes to it + let docs = getDefaultCharacterDocs(charId, characterFormData); + addDefaultDocs(docs); } return charId; }, diff --git a/app/imports/api/creature/properties/DamageMultipliers.js b/app/imports/api/creature/properties/DamageMultipliers.js index bf328d12..98032e00 100644 --- a/app/imports/api/creature/properties/DamageMultipliers.js +++ b/app/imports/api/creature/properties/DamageMultipliers.js @@ -1,12 +1,12 @@ import SimpleSchema from 'simpl-schema'; import {makeChild} from "/imports/api/parenting.js"; -DamageMultipliers = new Mongo.Collection("damageMultipliers"); +const DamageMultipliers = new Mongo.Collection("damageMultipliers"); /* * DamageMultipliers are whole numbered stats of a character */ -Schemas.DamageMultiplier = new SimpleSchema({ +const damageMultiplierSchema = new SimpleSchema({ charId: { type: String, regEx: SimpleSchema.RegEx.Id, @@ -30,7 +30,9 @@ Schemas.DamageMultiplier = new SimpleSchema({ }, }); -DamageMultipliers.attachSchema(Schemas.DamageMultiplier); +DamageMultipliers.attachSchema(damageMultiplierSchema); // DamageMultipliers.attachBehaviour("softRemovable"); makeChild(DamageMultipliers, ["enabled"]); //children of lots of things + +export default DamageMultipliers; diff --git a/app/imports/api/inventory/Items.js b/app/imports/api/inventory/Items.js index 41d819a5..ebbd38db 100644 --- a/app/imports/api/inventory/Items.js +++ b/app/imports/api/inventory/Items.js @@ -206,3 +206,4 @@ makeChild(Items); //children of containers makeParent(Items, ["name", "enabled"]); //parents of effects and attacks //Items.allow(CHARACTER_SUBSCHEMA_ALLOW); +export default Items; diff --git a/app/imports/ui/character/CharacterCreationDialog.vue b/app/imports/ui/character/CharacterCreationDialog.vue index e3084e19..e5539314 100644 --- a/app/imports/ui/character/CharacterCreationDialog.vue +++ b/app/imports/ui/character/CharacterCreationDialog.vue @@ -20,7 +20,7 @@ - + diff --git a/app/imports/ui/pages/CharacterList.vue b/app/imports/ui/pages/CharacterList.vue index fdc584c6..7b8d4618 100644 --- a/app/imports/ui/pages/CharacterList.vue +++ b/app/imports/ui/pages/CharacterList.vue @@ -78,6 +78,7 @@ import ToolbarLayout from "/imports/ui/layouts/ToolbarLayout.vue"; import LabeledFab from "/imports/ui/components/LabeledFab.vue"; import CharacterCreationDialog from "/imports/ui/character/CharacterCreationDialog.vue"; + import insertCreature from '/imports/api/creature/insertCreature.js'; const characterTransform = function(char){ char.url = `\/character\/${char._id}\/${char.urlName || "-"}`; @@ -123,15 +124,14 @@ }, }, methods: { - insertCharacter(e){ - console.log(e); + insertCharacter(){ store.commit("pushDialogStack", { component: CharacterCreationDialog, data: {}, element: undefined, returnElement: undefined, callback(result){ - console.log({result}); + insertCreature.call(result); }, }); },