DEV Community

Ephriam Henderson
Ephriam Henderson

Posted on

Day 9 [D&D Character Sheets]

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.

Top comments (0)