Got creature computation working again after moving to imports dir

This commit is contained in:
Stefan Zermatten
2018-10-12 11:59:29 +02:00
parent d117570165
commit 04059709eb
13 changed files with 168 additions and 186 deletions

View File

@@ -1,9 +1,11 @@
Attributes = new Mongo.Collection("attributes");
import {makeChild} from "/imports/api/parenting.js";
let Attributes = new Mongo.Collection("attributes");
/*
* Attributes are whole numbered stats of a character
*/
Schemas.Attribute = new SimpleSchema({
attributeSchema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
@@ -59,9 +61,6 @@ Schemas.Attribute = new SimpleSchema({
type: Boolean,
optional: true,
},
parent: {
type: Schemas.Parent
},
enabled: {
type: Boolean,
defaultValue: true,
@@ -78,10 +77,9 @@ Schemas.Attribute = new SimpleSchema({
},
});
Attributes.attachSchema(Schemas.Attribute);
Attributes.attachSchema(attributeSchema);
Attributes.attachBehaviour("softRemovable");
makeChild(Attributes, ["enabled"]); //children of lots of things
Attributes.allow(CHARACTER_SUBSCHEMA_ALLOW);
Attributes.deny(CHARACTER_SUBSCHEMA_DENY);
export default Attributes;

View File

@@ -1,9 +1,11 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import Effects from "/imports/api/creature/Effects.js"
import deathSaveSchema from "/imports/api/creature/subSchemas/DeathSaves.js"
//set up the collection for creatures
Creatures = new Mongo.Collection("creatures");
Schemas.Creature = new SimpleSchema({
let creatureSchema = new SimpleSchema({
//strings
name: {type: String, defaultValue: "", trim: false, optional: true},
urlName: {type: String, defaultValue: "-", trim: false, optional: true},
@@ -19,7 +21,7 @@ Schemas.Creature = new SimpleSchema({
backstory: {type: String, defaultValue: "", trim: false, optional: true},
//mechanics
deathSave: {type: Schemas.DeathSave},
deathSave: {type: deathSaveSchema},
xp: {type: Number, defaultValue: 0},
weightCarried: {type: Number, defaultValue: 0},
level: {type: Number, defaultValue: 0},
@@ -32,8 +34,9 @@ Schemas.Creature = new SimpleSchema({
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [], index: 1},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
defaultValue: "#9E9E9E",
// match hex colors of the form #A23 or #A23f56
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
},
//TODO add per-creature settings
//how many experiences to load at a time in XP table
@@ -57,7 +60,7 @@ Schemas.Creature = new SimpleSchema({
"settings.newUserExperience": {type: Boolean, optional: true},
});
Creatures.attachSchema(Schemas.Creature);
Creatures.attachSchema(creatureSchema);
Creatures.calculate = {
xpLevel: function(charId){
@@ -89,7 +92,7 @@ const insertCharacter = new ValidatedMethod({
"You need to be logged in to insert a creature");
}
// Create the creature document
// Create the creature document
Creatures.insert({name, owner: this.userId});
this.unblock();
//Add all the required attributes to it
@@ -141,6 +144,59 @@ if (Meteor.isServer){
doc.settings.newUserExperience = true;
}
});
//give ceatures default ceature effects
Creatures.after.insert(function(userId, char) {
if (Meteor.isServer) {
Effects.insert({
charId: char._id,
name: "Proficiency bonus by level",
stat: "proficiencyBonus",
operation: "add",
calculation: "floor(level / 4 + 1.75)",
parent: {
id: char._id,
collection: "Ceatures",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Dexterity Armor Bonus",
stat: "armor",
operation: "add",
calculation: "dexterityArmor",
parent: {
id: char._id,
collection: "Ceatures",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Armor",
stat: "armor",
operation: "base",
value: 10,
parent: {
id: char._id,
collection: "Ceatures",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Carrying Capacity",
stat: "carryMultiplier",
operation: "base",
value: "1",
parent: {
id: char._id,
collection: "Ceatures",
group: "Inate",
},
});
}
});
}
Creatures.allow({
@@ -166,3 +222,5 @@ Creatures.deny({
return _.contains(fields, "owner");
}
});
export default Creatures;

View File

@@ -1,10 +1,12 @@
import {makeChild} from "/imports/api/parenting.js";
Effects = new Mongo.Collection("effects");
/*
* Effects are reason-value attached to skills and abilities
* that modify their final value or presentation in some way
*/
Schemas.Effect = new SimpleSchema({
effectSchema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
@@ -41,10 +43,6 @@ Schemas.Effect = new SimpleSchema({
optional: true,
trim: false,
},
//the thing that created this effect
parent: {
type: Schemas.Parent
},
//which stat the effect is applied to
stat: {
type: String,
@@ -56,76 +54,9 @@ Schemas.Effect = new SimpleSchema({
},
});
Effects.attachSchema(Schemas.Effect);
Effects.attachSchema(effectSchema);
Effects.attachBehaviour("softRemovable");
makeChild(Effects, ["enabled"]); //children of lots of things
Effects.allow(CHARACTER_SUBSCHEMA_ALLOW);
Effects.deny(CHARACTER_SUBSCHEMA_DENY);
//give characters default character effects
Characters.after.insert(function(userId, char) {
if (Meteor.isServer) {
Effects.insert({
charId: char._id,
name: "Constitution modifier for each level",
stat: "hitPoints",
operation: "add",
calculation: "level * constitutionMod",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Proficiency bonus by level",
stat: "proficiencyBonus",
operation: "add",
calculation: "floor(level / 4 + 1.75)",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Dexterity Armor Bonus",
stat: "armor",
operation: "add",
calculation: "dexterityArmor",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Armor",
stat: "armor",
operation: "base",
value: 10,
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Carrying Capacity",
stat: "carryMultiplier",
operation: "base",
value: "1",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
}
});
export default Effects;

View File

@@ -1,10 +1,12 @@
import {makeChild} from "/imports/api/parenting.js";
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
*/
Schemas.Skill = new SimpleSchema({
skillSchema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
@@ -65,19 +67,15 @@ Schemas.Skill = new SimpleSchema({
type: Number,
optional: true,
},
parent: {
type: Schemas.Parent
},
enabled: {
type: Boolean,
defaultValue: true,
},
});
Skills.attachSchema(Schemas.Skill);
Skills.attachSchema(skillSchema);
Skills.attachBehaviour("softRemovable");
makeChild(Skills, ["enabled"]); //children of lots of things
Skills.allow(CHARACTER_SUBSCHEMA_ALLOW);
Skills.deny(CHARACTER_SUBSCHEMA_DENY);
export default Skills;

View File

@@ -1,32 +1,36 @@
// TODO allow abilities to get disadvantage, making all skills that are based
// TODO allow abilities to get advantage/disadvantage, making all skills that are based
// on them disadvantaged as well
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import Creatures from "/imports/api/creature/Creatures.js";
import Attributes from "/imports/api/creature/Attributes.js";
import Skills from "/imports/api/creature/Skills.js";
import Effects from "/imports/api/creature/Effects.js";
const recomputeCharacter = new ValidatedMethod({
const recomputeCreature = new ValidatedMethod({
name: "Characters.methods.recomputeCharacter",
name: "Creatures.methods.recomputeCreature",
validate: new SimpleSchema({
charId: { type: String }
}).validator(),
run({charId}) {
if (!canEditCharacter(charId, this.userId)) {
throw new Meteor.Error('Characters.methods.recomputeCharacter.denied',
'You do not have permission to recompute this character');
if (!canEditCreature(charId, this.userId)) {
throw new Meteor.Error('Creatures.methods.recomputeCreature.denied',
'You do not have permission to recompute this creature');
}
computeCharacterById(charId);
computeCreatureById(charId);
},
});
/*
* This function is the heart of DiceCloud. It recomputes a character's stats,
* This function is the heart of DiceCloud. It recomputes a creature's stats,
* distilling down effects and proficiencies into the final stats that make up
* a character.
* a creature.
*
* Essentially this is a backtracking algorithm that computes stats'
* dependencies before computing stats themselves, while detecting
@@ -41,7 +45,7 @@ const recomputeCharacter = new ValidatedMethod({
* expanded to meet demand.
*
* A brief overview:
* - Fetch the stats of the character and add them to
* - Fetch the stats of the creature and add them to
* an object for quick lookup
* - Fetch the effects and proficiencies which apply to each stat and store them with the stat
* - Fetch the class levels and store them as well
@@ -59,21 +63,21 @@ const recomputeCharacter = new ValidatedMethod({
* - Mark the stat as computed
* - Write the computed results back to the database
*/
const computeCharacterById = function (charId){
let char = buildCharacter();
char = computeCharacter(char);
writeCharacter(char);
const computeCreatureById = function (charId){
let char = buildCreature();
char = computeCreature(char);
writeCreature(char);
return char;
};
/*
* Write the in-memory character to the database docs
* Write the in-memory creature to the database docs
*/
const writeCharacter = function (char) {
const writeCreature = function (char) {
writeAttributes(char);
writeSkills(char);
writeDamageMultipliers(char);
Characters.update(char.id, {$set: {level: char.level}});
Creatures.update(char.id, {$set: {level: char.level}});
};
/*
@@ -157,10 +161,10 @@ const writeDamageMultipliers = function (char) {
}
/*
* Get the character's data from the database and build an in-memory model that
* Get the creature's data from the database and build an in-memory model that
* can be computed. Hits 6 database collections with indexed queries.
*/
const buildCharacter = function (charId){
const buildCreature = function (charId){
let char = {
id: charId,
atts: {},
@@ -169,7 +173,7 @@ const buildCharacter = function (charId){
classes: {},
level: 0,
};
// Fetch the attributes of the character and add them to an object for quick lookup
// Fetch the attributes of the creature and add them to an object for quick lookup
Attributes.find({charId}).forEach(attribute => {
if (!char.atts[attribute.variableName]){
char.atts[attribute.variableName] = {
@@ -190,7 +194,7 @@ const buildCharacter = function (charId){
}
});
// Fetch the skills of the character and store them
// Fetch the skills of the creature and store them
Skills.find({charId}).forEach(skill => {
if (!char.skills[skill.variableName]){
char.skills[skill.variableName] = {
@@ -215,7 +219,7 @@ const buildCharacter = function (charId){
}
});
// Fetch the damage multipliers of the character and store them
// Fetch the damage multipliers of the creature and store them
DamageMultipliers.find({charId}).forEach(damageMultiplier =>{
if (!char.dms[damageMultiplier.variableName]){
char.dms[damageMultiplier.variableName] = {
@@ -277,9 +281,9 @@ const buildCharacter = function (charId){
}
/*
* Compute the character's stats in-place, returns the same char object
* Compute the creature's stats in-place, returns the same char object
*/
export const computeCharacter = function (char){
const computeCreature = function (char){
// Iterate over each stat in order and compute it
for (statName in char.atts){
let stat = char.atts[statName]
@@ -297,7 +301,7 @@ export const computeCharacter = function (char){
}
/*
* Compute a single stat on a character
* Compute a single stat on a creature
*/
const computeStat = function(stat, char){
// If the stat is already computed, skip it
@@ -329,7 +333,7 @@ const computeStat = function(stat, char){
}
/*
* Compute a single effect on a character
* Compute a single effect on a creature
*/
const computeEffect = function(effect, char){
if (effect.computed) return;
@@ -516,7 +520,7 @@ const evaluateCalculation = function(string, char){
var className = sub.replace(/levels?$/, "");
return char.classes[className] && char.classes[className].level || sub;
}
// Character level
// Creature level
if (sub === "level"){
return char.level;
}
@@ -533,18 +537,18 @@ const evaluateCalculation = function(string, char){
}
};
const recomputeCharacterXP = new ValidatedMethod({
name: "Characters.methods.recomputeCharacterXP",
const recomputeCreatureXP = new ValidatedMethod({
name: "Creatures.methods.recomputeCreatureXP",
validate: new SimpleSchema({
charId: { type: String }
}).validator(),
run({charId}) {
if (!canEditCharacter(charId, this.userId)) {
if (!canEditCreature(charId, this.userId)) {
// Throw errors with a specific error code
throw new Meteor.Error("Characters.methods.recomputeCharacterXP.denied",
"You do not have permission to recompute this character's XP");
throw new Meteor.Error("Creatures.methods.recomputeCreatureXP.denied",
"You do not have permission to recompute this creature's XP");
}
var xp = 0;
Experiences.find(
@@ -554,23 +558,23 @@ const recomputeCharacterXP = new ValidatedMethod({
xp += e.value;
});
Characters.update(charId, {$set: {xp}})
Creatures.update(charId, {$set: {xp}})
return xp;
},
});
const recomputeCharacterWeightCarried = new ValidatedMethod({
name: "Character.methods.recomputeCharacterWeightCarried",
const recomputeCreatureWeightCarried = new ValidatedMethod({
name: "Creature.methods.recomputeCreatureWeightCarried",
validate: new SimpleSchema({
charId: { type: String }
}).validator(),
run({charId}){
if (!canEditCharacter(charId, this.userId)) {
if (!canEditCreature(charId, this.userId)) {
// Throw errors with a specific error code
throw new Meteor.Error("Characters.methods.recomputeCharacterWeightCarried.denied",
"You do not have permission to recompute this character's carried weight");
throw new Meteor.Error("Creatures.methods.recomputeCreatureWeightCarried.denied",
"You do not have permission to recompute this creature's carried weight");
}
var weightCarried = 0;
// store a dictionary of carried containers
@@ -603,7 +607,14 @@ const recomputeCharacterWeightCarried = new ValidatedMethod({
}
});
Characters.update(charId, {$set: {weightCarried}})
Creatures.update(charId, {$set: {weightCarried}})
return weightCarried;
}
});
export {
recomputeCreature,
computeCreature,
recomputeCreatureXP,
recomputeCreatureWeightCarried
};

View File

@@ -1,4 +1,4 @@
import {computeCharacter} from "./CharacterComputation.js";
import {computeCreature} from "./creatureComputation.js";
import assert from "assert";
const makeEffect = function(operation, value){
@@ -11,8 +11,8 @@ const makeEffect = function(operation, value){
return effect;
}
describe('computeCharacter', function () {
it('computes an aritrary character', function () {
describe('computeCreature', function () {
it('computes an aritrary creature', function () {
let char = {
atts: {
attribute1: {
@@ -90,7 +90,7 @@ describe('computeCharacter', function () {
},
level: 5,
};
char = computeCharacter(char);
char = computeCreature(char);
console.log(char);
assert(true);
});

View File

@@ -0,0 +1,166 @@
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: []};
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),
});
}
return docs;
}

View File

@@ -1,21 +0,0 @@
/*
* Adjustments make instantaneous changes to the value of some attribute
* Damage, healing and resource cost/recovery are all adjustments
*/
Schemas.Adjustment = new SimpleSchema({
//which stat the adjustment is applied to
stat: {
type: String,
optional: true,
},
//the value added to the stat
value: {
type: Number,
decimal: true,
optional: true,
},
calculation: {
type: String,
optional: true,
},
});

View File

@@ -1,13 +0,0 @@
Schemas.Attribute = new SimpleSchema({
//the temporary shift of the attribute
//should be zero after reset
adjustment: {
type: Number,
defaultValue: 0,
},
reset: {
type: String,
defaultValue: "longRest",
allowedValues: ["longRest", "shortRest"],
},
});

View File

@@ -1,4 +1,4 @@
Schemas.DeathSave = new SimpleSchema({
const deathSaveSchema = new SimpleSchema({
pass: {
type: Number,
min: 0,
@@ -20,3 +20,5 @@ Schemas.DeathSave = new SimpleSchema({
defaultValue: false,
},
});
export default deathSaveSchema;

View File

@@ -1,4 +0,0 @@
Schemas.Skill = new SimpleSchema({
//attribute name that this skill used as base mod for roll
ability: {type: String, defaultValue: ""},
});