Day 9 [D&D Character Sheets]
Tags: 100daysofcode
Published: 2020-03-21 06:44:36

# 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 <span style="background-color: black; color: white;"> m = &lfloor;(a - 10) / 2&rfloor;</span>  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: <span style="background-color: black; color: white;"> &lfloor;(13 - 10) / 2&rfloor; = &lfloor;1.5&rfloor; = 1</span>. 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 <span style="background-color: black; color: white;"> 1d20 + 1</span>.
>
> 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.



```js
// 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: 

```js
// skill before
acrobatics: {
				ability: {
					type: String,
					default: "dexterity"
				},
				skill: Skill
			},         
```

```js
// 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.

```js
// 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
			}
		})
```



```js
// 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

{% github ApproachingApathy/100days-dnd-character-sheets %}

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.