Report
Today I worked on my character schema. Yesterday you may remember my Character.create() being absolutely massive and completely unwieldy. By using a combination of default values, and values calculated by middleware, I was able to shrink it significantly.
The first thing I did was write some mongoose middleware to calculate those skills fields. This middleware runs every time the document is saved. I calculate each skill's ability score and get their speed from their race description.
In D&D every creature has a set of ability scores and ability modifiers derived from these ability scores. The formula is m = ⌊(a - 10) / 2⌋ where m and a are the ability modifier and relevant ability score. Ability modifiers are used to augment die rolls. For instance, if a creature has a charisma score of 13 the modifier is calculated thusly: ⌊(13 - 10) / 2⌋ = ⌊1.5⌋ = 1. If they try to lie to another creature they may be asked to roll deception in which case they would roll a single 20-sided dice or 1d20 and add their ability modifier and any bonuses, in this case our character has no bonuses so their roll equals 1d20 + 1.
Each skill: acrobatics, investigation, persuasion, etcetera has its own modifier with its own possible bonuses. Every skill has an associated ability; deception uses charisma, insight uses wisdom, investigation uses intelligence.
// db/models/character/index.js
// Interestingly arrow functions won't work here, seemingly
// because they don't allow the binding nessesary to access
// the 'this' object.
Character.pre('save', function(next, docs) {
// Loop through skills
Object.keys(this.stats.skills).forEach(key => {
let skill = this.stats.skills[key]
// Calculate modifier
skill.skill.value = Math.floor(
(this.stats.abilities[skill.ability] - 10) / 2
)
})
let Race = this.model('Race')
// Get the characters speed from their race.
// This will likely be changed because the
// character speed may change.
Race.findOne({ _id: this.race })
.then(characterRace => {
this.stats.speed = characterRace.stats.speed
})
.then(() => {
next()
})
})
I also had to change the schema to ensure that the relevant fields exist even when not explicitly defined:
// skill before
acrobatics: {
ability: {
type: String,
default: "dexterity"
},
skill: Skill
},
// skill after
acrobatics: {
ability: {
type: String,
default: "dexterity"
},
skill: {
type: Skill,
default: Skill
}
},
I also add defaults wherever it made sense to. Resulting in a much smaller footprint for my create function.
// Character.create() before
await db.models.Character.create({
shortName: "Aria",
longName: "Aria Wyndsong",
race: elf.id,
"class": await db.models.Class.findOne({name: "Warlock"}).id,
player: player.id,
description: "Aria is awesome.",
age: 72,
urlSlug: "",
stats: {
abilities: {
strength: 10,
dexterity: 12,
constitution: 10,
intelligence: 13,
wisdom: 14,
charisma: 16,
},
skills: {
acrobatics: {
skill: {
proficiency: false,
value: 1,
additionalModifiers: []
}
},
animalHandling: {
skill: {
proficiency: false,
value: 2,
additionalModifiers: []
}
},
arcana: {
skill: {
proficiency: true,
value: 1,
additionalModifiers: []
}
},
athletics: {
skill: {
proficiency: false,
value: 0,
additionalModifiers: []
}
},
deception: {
skill: {
proficiency: false,
value: 3,
additionalModifiers: []
}
},
history: {
skill: {
proficiency: true,
value: 1,
additionalModifiers: []
}
},
insight: {
skill: {
proficiency: true,
value: 2,
additionalModifiers: []
}
},
intimidation: {
skill: {
proficiency: false,
value: 3,
additionalModifiers: []
}
},
acrobatics: {
skill: {
proficiency: false,
value: 1,
additionalModifiers: []
}
},
investigation: {
skill: {
proficiency: true,
value: 1,
additionalModifiers: []
}
},
medicine: {
skill: {
proficiency: false,
value: 2,
additionalModifiers: []
}
},
perception: {
skill: {
proficiency: true,
value: 2,
additionalModifiers: []
}
},
persuasion: {
skill: {
proficiency: false,
value: 3,
additionalModifiers: []
}
},
religion: {
skill: {
proficiency: false,
value: 1,
additionalModifiers: []
}
},
sleightOfHand: {
skill: {
proficiency: false,
value: 1,
additionalModifiers: []
}
},
stealth: {
skill: {
proficiency: false,
value: 1,
additionalModifiers: []
}
},
survival: {
skill: {
proficiency: false,
value: 2,
additionalModifiers: []
}
}
},
speed: elf.stats.speed,
proficiencyBonus: 2
}
})
// Character.create() after
await db.models.Character.create({
shortName: "Aria",
longName: "Aria Wyndsong",
race: elf.id,
class: warlock.id,
player: player.id,
description: "Aria is awesome.",
age: 72,
urlSlug: "",
stats: {
abilities: {
strength: 10,
dexterity: 12,
constitution: 10,
intelligence: 13,
wisdom: 14,
charisma: 16,
}
}
})
Project
[100days] The DND Character Sheet App
This is the first project of my 100 days of coding
This is an app to keep D&D character sheets.
Stack
I'll be using Node.js and building a full-stack Express app with MongoDB.
Requirements
Minimum Viable
-
Present a D&D Character Sheet
-
The sheet should display all the same info as the first page of the 5e Official sheet.
-
Users should be able to log in and create player-characters.
-
Users should be able to edit character sheets.
-
Users should be able to organize character sheets into groups (parties/tables)
-
Sheets should auto calculate basic stats like ability modifiers
-
Support Proficiency Bonuses
Cake
-
Extend character creation to allow the user to use any of the three common stat gen methods
-
Point Buy
-
Standard Array
-
Roll
-
Extend the character sheet to all the info in the 5e official sheet.
-
Allow for image uploads for character portraits.
-
Allow for…
The First project will be an app to keep D&D character sheets.
Stack
I'll be using Node.js and building a full-stack Express app with MongoDB.
Requirements
Minimum Viable
- [ ] Investigate automating or finding a source of info for the data in the SRD.
- [ ] Present a D&D Character Sheet
- [ ] The sheet should display all the same info as the first page of the 5e Official sheet.
- [ ] Users should be able to log in and create player-characters.
- [ ] Users should be able to edit character sheets.
- [ ] Users should be able to organize character sheets into groups (parties/tables)
- [ ] Sheets should auto calculate basic stats like ability modifiers.
- [ ] Support Proficiency Bonuses
Cake
- [ ] Extend character creation to allow the user to use any of the three common stat gen methods.
- [ ] Point Buy
- [ ] Standard Array
- [ ] Roll
- [ ] Extend the character sheet to all the info in the 5e official sheet.
- [ ] Allow for image uploads for character portraits.
- [ ] Allow for extended descriptions/backstories.
- [ ] Characters should have nice full page backstories.
- [ ] Preferably use a markdown editor.