V1.31.0: armor holds Dexterity back

- All of a Dexterity modifier counts toward AC in light armor, at most +2
  in medium armor, none in heavy armor, going by the equipped item's kind
- The gear card says when the armor is holding something back
- Library and seed armor notes and docs brought in line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 11:24:28 +02:00
co-authored by Claude Opus 5.5
parent 16b5ac05ca
commit cbda10668b
15 changed files with 136 additions and 51 deletions
+2
View File
@@ -104,6 +104,8 @@ including a character sheet or an encounter) and **Account**.
Aura of Protection, Diamond Soul, Slippery Mind and Jack of All Trades are
counted on the sheet, by what is equipped (armor and shields are marked
items) and by level in the class that gives them.
- **Armor holds Dexterity back** as in the rules: all of it counts toward AC
in light armor, at most +2 in medium, none in heavy.
- **Stricter battles.** A spell with no slot of its level or higher left
cannot be cast (cantrips never need one), and a player changes the weapon
in hand only as the battle begins or on their own turn after attacking.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "backend",
"version": "1.30.0",
"version": "1.31.0",
"private": true,
"type": "commonjs",
"main": "src/index.js",
File diff suppressed because one or more lines are too long
+22 -2
View File
@@ -73,6 +73,15 @@ function armorClass(dexMod, armorBonus = 0) {
return 10 + dexMod + armorBonus;
}
// How much of a Dexterity modifier counts toward AC in the armor worn: all
// of it in light armor or none, at most +2 in medium armor, and none of it
// (a penalty included) in heavy armor.
function armorDexterity(dexMod, worn = null) {
if (worn === 'HEAVY') return 0;
if (worn === 'MEDIUM') return Math.min(dexMod, 2);
return dexMod;
}
function initiative(dexMod) {
return dexMod;
}
@@ -290,7 +299,9 @@ function activeGear(character) {
// The armor on their back (the heaviest, should there be two) and whether
// a shield is on their arm: class features turn on what is worn.
const kinds = held.map((h) => h.entry.armorType).filter(Boolean);
const armor = { worn: ['HEAVY', 'MEDIUM', 'LIGHT'].find((kind) => kinds.includes(kind)) || null, shield: kinds.includes('SHIELD') };
const worn = ['HEAVY', 'MEDIUM', 'LIGHT'].find((kind) => kinds.includes(kind)) || null;
const suit = worn ? held.find((h) => h.entry.armorType === worn) : null;
const armor = { worn, name: suit ? suit.entry.name : null, shield: kinds.includes('SHIELD') };
for (const { entry } of held) {
for (const effect of Array.isArray(entry.effects) ? entry.effects : []) {
if (GEAR_STATS.includes(effect.stat) && Number.isInteger(effect.amount) && effect.amount !== 0) {
@@ -358,6 +369,14 @@ function computeCharacterSheet(character, skillProficiencies = [], saveProficien
// What their class features do to the numbers below (classFeatures.js).
const modifiers = Object.fromEntries(ABILITIES.map((ability) => [ability, abilities[ability].modifier]));
const features = classFeatureEffects(classes, modifiers, profBonus, gear.armor);
// Medium and heavy armor hold Dexterity back; said on the gear card when it bites.
const acDex = armorDexterity(abilities.DEX.modifier, gear.armor.worn);
if (acDex !== abilities.DEX.modifier) {
gear.notes.push({
source: gear.armor.name,
note: gear.armor.worn === 'HEAVY' ? 'Heavy armor: Dexterity does not count toward AC.' : 'Medium armor: at most +2 from Dexterity counts toward AC.',
});
}
const saves = {};
for (const ability of ABILITIES) {
@@ -398,7 +417,7 @@ function computeCharacterSheet(character, skillProficiencies = [], saveProficien
abilities,
proficiencyBonus: profBonus,
// Unarmored Defense and the like replace the 10 + DEX that AC starts from.
armorClass: (features.acBase !== null ? features.acBase + armorBonus : armorClass(abilities.DEX.modifier, armorBonus)) + fromGear('AC'),
armorClass: (features.acBase !== null ? features.acBase + armorBonus : armorClass(acDex, armorBonus)) + fromGear('AC'),
initiative: initiative(abilities.DEX.modifier) + features.halfProficiency + fromGear('INITIATIVE'),
// A speed the DM has set is their last word: a class's extra speed is
// added to the species' own speed only.
@@ -565,6 +584,7 @@ module.exports = {
passivePerception,
passiveScore,
armorClass,
armorDexterity,
initiative,
hpMax,
spellSaveDC,
+34
View File
@@ -132,3 +132,37 @@ describe('multiclassed', () => {
expect(effects).toEqual({ acBase: null, speed: 0, hp: 0, saveBonus: 0, saveProficiencies: [], halfProficiency: 0, inForce: [] });
});
});
describe('Dexterity in armor', () => {
const { armorDexterity } = require('./calculations');
// Dexterity 18 (+4), and a clumsy one at 8 (-1).
const nimble = (inventoryItems) => sheet({ characterClass: 'FIGHTER', dexScore: 17, inventoryItems });
const clumsy = (inventoryItems) => sheet({ characterClass: 'FIGHTER', dexScore: 7, inventoryItems });
test('all of it in light armor or none, at most +2 in medium, none in heavy', () => {
expect([null, 'LIGHT', 'MEDIUM', 'HEAVY', 'SHIELD'].map((worn) => armorDexterity(4, worn))).toEqual([4, 4, 2, 0, 4]);
expect([null, 'LIGHT', 'MEDIUM', 'HEAVY'].map((worn) => armorDexterity(-1, worn))).toEqual([-1, -1, -1, 0]);
});
test('the sheet’s AC follows, and the gear card says why', () => {
expect(nimble([]).armorClass).toBe(14);
expect(nimble([wearing('Leather Armor', 'LIGHT', 1)]).armorClass).toBe(15);
const scale = nimble([wearing('Scale Mail', 'MEDIUM', 4)]);
expect(scale.armorClass).toBe(16); // 10 + 2 of the 4 + 4
expect(scale.gear.notes).toEqual([{ source: 'Scale Mail', note: 'Medium armor: at most +2 from Dexterity counts toward AC.' }]);
const plate = nimble([wearing('Plate Armor', 'HEAVY', 8), wearing('Shield', 'SHIELD', 2)]);
expect(plate.armorClass).toBe(20); // 18, and the shield
expect(plate.gear.notes[0]).toEqual({ source: 'Plate Armor', note: 'Heavy armor: Dexterity does not count toward AC.' });
// Heavy armor hides a Dexterity penalty too; medium does not.
expect(clumsy([wearing('Plate Armor', 'HEAVY', 8)]).armorClass).toBe(18);
expect(clumsy([wearing('Scale Mail', 'MEDIUM', 4)]).armorClass).toBe(13);
// Nothing to say when the armor holds nothing back, and only Dexterity's part in AC changes.
expect(sheet({ characterClass: 'FIGHTER', inventoryItems: [wearing('Scale Mail', 'MEDIUM', 4)] }).gear.notes).toEqual([]);
expect(plate.initiative).toBe(4);
expect(plate.saves.DEX.bonus).toBe(4);
});
test('an item with +AC that is not marked as armor holds nothing back', () => {
expect(nimble([wearing('Ring of Protection', null, 1)]).armorClass).toBe(15);
});
});
+15 -14
View File
@@ -3,11 +3,12 @@
// matches the `Item` Prisma model minus `id`/`campaignId`, plus a stable `key`
// for "add to campaign" lookups and a `category` for the library picker.
//
// Armor here is a flat bonus to AC on top of 10 + Dexterity, because that is
// how gear effects work (game/calculations.js, GEAR_STATS): the standard
// rules' "replaces 10 + DEX" and "DEX bonus capped at 2" are not modelled.
// Each suit's bonus is its standard AC less 10, and what the bonus cannot say
// is in the note. A DM who wants it stricter edits the copy in their campaign.
// Armor's AC is a bonus on top of 10 + Dexterity, because that is how gear
// effects work (game/calculations.js, GEAR_STATS): each suit's bonus is its
// standard AC less 10. What kind of armor it is (armorType, from the category)
// tells the sheet how much Dexterity counts in it: all of it in light armor,
// at most +2 in medium, none in heavy (calculations.js, armorDexterity).
// What neither can say (a Strength requirement, Stealth) is in the note.
const STEALTH = 'Disadvantage on Stealth checks.';
const ARMOR_TYPE = { 'Light Armor': 'LIGHT', 'Medium Armor': 'MEDIUM', 'Heavy Armor': 'HEAVY', Shields: 'SHIELD' };
@@ -39,17 +40,17 @@ const ITEM_LIBRARY = [
armor('studded-leather', 'Studded Leather', 'Light Armor', 2, 'Tough leather set with close rivets and spikes.'),
// Medium armor
armor('hide-armor', 'Hide Armor', 'Medium Armor', 2, 'Thick furs and pelts.', 'Standard rules: counts at most +2 from Dexterity.'),
armor('chain-shirt', 'Chain Shirt', 'Medium Armor', 3, 'Interlocking rings worn between layers of clothing.', 'Standard rules: counts at most +2 from Dexterity.'),
armor('scale-mail', 'Scale Mail', 'Medium Armor', 4, 'A coat and leggings of leather covered in overlapping metal scales.', `${STEALTH} Standard rules: counts at most +2 from Dexterity.`),
armor('breastplate', 'Breastplate', 'Medium Armor', 4, 'A fitted metal chest piece worn with supple leather.', 'Standard rules: counts at most +2 from Dexterity.'),
armor('half-plate', 'Half Plate', 'Medium Armor', 5, 'Shaped metal plates covering most of the body.', `${STEALTH} Standard rules: counts at most +2 from Dexterity.`),
armor('hide-armor', 'Hide Armor', 'Medium Armor', 2, 'Thick furs and pelts.', 'Medium armor: at most +2 from Dexterity counts (worked out on the sheet).'),
armor('chain-shirt', 'Chain Shirt', 'Medium Armor', 3, 'Interlocking rings worn between layers of clothing.', 'Medium armor: at most +2 from Dexterity counts (worked out on the sheet).'),
armor('scale-mail', 'Scale Mail', 'Medium Armor', 4, 'A coat and leggings of leather covered in overlapping metal scales.', `${STEALTH} Medium armor: at most +2 from Dexterity counts (worked out on the sheet).`),
armor('breastplate', 'Breastplate', 'Medium Armor', 4, 'A fitted metal chest piece worn with supple leather.', 'Medium armor: at most +2 from Dexterity counts (worked out on the sheet).'),
armor('half-plate', 'Half Plate', 'Medium Armor', 5, 'Shaped metal plates covering most of the body.', `${STEALTH} Medium armor: at most +2 from Dexterity counts (worked out on the sheet).`),
// Heavy armor: slow, loud, and in the standard rules it ignores Dexterity.
armor('ring-mail', 'Ring Mail', 'Heavy Armor', 4, 'Leather armor with heavy rings sewn into it.', `${STEALTH} Standard rules: adds no Dexterity.`),
armor('chain-mail', 'Chain Mail', 'Heavy Armor', 6, 'Interlocking metal rings over a layer of quilted fabric, with gauntlets.', `${STEALTH} Standard rules: adds no Dexterity, and needs Strength 13.`, [{ stat: 'SPEED', amount: -10 }]),
armor('splint-armor', 'Splint Armor', 'Heavy Armor', 7, 'Narrow vertical strips of metal riveted to a leather backing.', `${STEALTH} Standard rules: adds no Dexterity, and needs Strength 15.`, [{ stat: 'SPEED', amount: -10 }]),
armor('plate-armor', 'Plate Armor', 'Heavy Armor', 8, 'Shaped, interlocking metal plates covering the whole body.', `${STEALTH} Standard rules: adds no Dexterity, and needs Strength 15.`, [{ stat: 'SPEED', amount: -10 }]),
armor('ring-mail', 'Ring Mail', 'Heavy Armor', 4, 'Leather armor with heavy rings sewn into it.', `${STEALTH} Heavy armor: Dexterity does not count (worked out on the sheet).`),
armor('chain-mail', 'Chain Mail', 'Heavy Armor', 6, 'Interlocking metal rings over a layer of quilted fabric, with gauntlets.', `${STEALTH} Heavy armor: Dexterity does not count (worked out on the sheet). Standard rules: needs Strength 13.`, [{ stat: 'SPEED', amount: -10 }]),
armor('splint-armor', 'Splint Armor', 'Heavy Armor', 7, 'Narrow vertical strips of metal riveted to a leather backing.', `${STEALTH} Heavy armor: Dexterity does not count (worked out on the sheet). Standard rules: needs Strength 15.`, [{ stat: 'SPEED', amount: -10 }]),
armor('plate-armor', 'Plate Armor', 'Heavy Armor', 8, 'Shaped, interlocking metal plates covering the whole body.', `${STEALTH} Heavy armor: Dexterity does not count (worked out on the sheet). Standard rules: needs Strength 15.`, [{ stat: 'SPEED', amount: -10 }]),
// Shields
armor('shield', 'Shield', 'Shields', 2, 'Wood or metal, carried in one hand.'),
+4 -3
View File
@@ -2638,7 +2638,7 @@ describe('gear effects', () => {
const quick = async (auth = playerAuth) => (await request(app).get(`${ch}/quick-stats`).set(auth)).body.quickStats;
const before = (await sheet()).sheet;
expect(before.gear).toEqual({ effects: [], notes: [], armor: { worn: null, shield: false } });
expect(before.gear).toEqual({ effects: [], notes: [], armor: { worn: null, name: null, shield: false } });
// Only the DM says what gear does, and only with numbers the sheet has.
const mail = {
@@ -4234,11 +4234,12 @@ describe('class features on the sheet', () => {
expect(s).toMatchObject({ armorClass: 14, speed: 40 });
expect(s.features.map((f) => f.name)).toEqual(['Fast Movement']);
// The DM says what kind of thing an item is. Heavy: the extra speed goes. Not armor: all of it is back, on top of the +2.
// The DM says what kind of thing an item is. Heavy: the extra speed goes, and Dexterity
// no longer counts (10 + the armor's 2). Not armor: all of it is back, on top of the +2.
expect((await request(app).patch(`${c}/items/${hide.id}`).set(playerAuth).send({ armorType: 'HEAVY' })).status).toBe(403);
expect((await request(app).patch(`${c}/items/${hide.id}`).set(dmAuth).send({ armorType: 'PLATE' })).status).toBe(400);
await request(app).patch(`${c}/items/${hide.id}`).set(dmAuth).send({ armorType: 'HEAVY' });
expect(await sheet()).toMatchObject({ armorClass: 14, speed: 30 });
expect(await sheet()).toMatchObject({ armorClass: 12, speed: 30 });
await request(app).patch(`${c}/items/${hide.id}`).set(dmAuth).send({ armorType: null });
expect(await sheet()).toMatchObject({ armorClass: 17, speed: 40 });
+18 -4
View File
@@ -816,7 +816,7 @@ themselves.
| Stat | Formula |
|---|---|
| Armor Class | 10 + DEX modifier (or what a class feature gives without armor, below) + equipped gear + the DM's extra AC |
| Armor Class | 10 + DEX modifier (as much of it as the armor worn lets through; or what a class feature gives without armor, below) + equipped gear + the DM's extra AC |
| Initiative | DEX modifier + a bard's Jack of All Trades + equipped gear |
| Speed | Species speed + a class's extra speed, unless a speed override is set, + equipped gear |
| Passive Perception | 10 + Perception skill bonus + equipped gear |
@@ -834,6 +834,21 @@ item is doing what. An effect on an ability score changes the score itself,
so everything in sections 5 to 10 that is built on that ability moves too.
The full rules are in [ITEMS.md](ITEMS.md), section 5.
**Dexterity in armor.** How much of the DEX modifier counts toward AC goes by
the armor that is equipped (items are marked light, medium or heavy:
[ITEMS.md](ITEMS.md), section 5):
| Armor worn | DEX modifier counted toward AC |
|---|---|
| None, or light | All of it |
| Medium | At most +2 (a penalty counts in full) |
| Heavy | None of it, a penalty included |
Only AC is affected: initiative, Dexterity saves and skills use the full
modifier. When the armor is holding something back, the "From Equipped Gear"
card says so. A shield changes nothing here, and neither does an item with
+AC that is not marked as armor.
### Class features that are counted
These class features change a number, so the sheet works them out. Nobody
@@ -1381,9 +1396,8 @@ calculations for them.
rest and lists conditions, but applies none of their effects
- Counters for species traits, talents and Boons, and uses the special
abilities give "a few times per day" beyond the ones in section 7
- Armor rules beyond a flat bonus to AC: medium armor does not cap Dexterity
and heavy armor does not ignore it. (Being unarmored is modelled: see
"Class features that are counted" in section 8)
- Armor proficiency, a Strength score needed for heavy armor, and
disadvantage on Stealth (these are notes on the armor)
- Weapon proficiency (everyone is proficient with everything)
- Shared rolls: a roll made from the sheet is seen only by whoever clicked,
and is not recorded
+5
View File
@@ -66,6 +66,11 @@ passivePerception = 10 + perceptionSkillBonus
armorClass = 10 + dexModifier + armorBonus
```
`dexModifier` here is as much of it as the equipped armor lets through
(`armorDexterity`): all of it with none or light armor, at most +2 in medium
armor, none in heavy armor. Armor is an item marked with its kind
(`Item.armorType`); its own AC is a gear effect.
Without armor, a class feature can replace the `10 + dexModifier` part:
Unarmored Defense (barbarian: `10 + DEX + CON`; monk, also without a shield:
`10 + DEX + WIS`) and Draconic Resilience (`13 + DEX`). The best one is used,
+20 -17
View File
@@ -170,8 +170,11 @@ for a penalty, on one of the sheet's numbers:
**Armor and shields are marked as such.** An item's pop-out has an **Armor**
menu: not armor (the default), light, medium, heavy, or shield. Its AC is
still an effect (+AC); the mark says what kind of thing it is, because some
class features depend on what is worn: a barbarian's and a monk's Unarmored
still an effect (+AC, its usual AC less 10); the mark says what kind of thing
it is. The sheet counts **all** of a character's Dexterity modifier toward AC
in light armor, **at most +2** in medium armor and **none** in heavy armor
([CHARACTERS.md](CHARACTERS.md), section 8), so plate is +8 and comes to AC
18 whoever wears it. Some class features depend on what is worn too: a barbarian's and a monk's Unarmored
Defense and a draconic sorcerer's scales stop while armor is equipped, a
monk's also stop behind a shield, and a barbarian's extra speed stops in
heavy armor ([CHARACTERS.md](CHARACTERS.md), section 8). The library's armor
@@ -254,8 +257,8 @@ and [ENCOUNTERS.md](ENCOUNTERS.md), section 10.
## 9. What is not modelled
- Armor rules beyond a flat bonus: armor does not replace 10 + DEX, cap the
DEX bonus or need a Strength score (set the AC effect to match)
- A Strength score needed for heavy armor, armor proficiency and Stealth
disadvantage: these are notes on the armor for the table to apply
- Effects on attack rolls, damage, spell save DC or proficiency bonus
- Effects that depend on something (only at night, only against undead), or
that grow with the item's level
@@ -275,10 +278,10 @@ and [ENCOUNTERS.md](ENCOUNTERS.md), section 10.
Everything "📚 From Library" offers. "While equipped" is what the item does
to the sheet of whoever has it on.
Armor is a flat bonus to AC on top of 10 + Dexterity: each suit's bonus is its
standard AC less 10. What a flat bonus cannot say (heavy armor ignoring
Dexterity, a Strength requirement, disadvantage on Stealth) is in the note,
for the DM to apply or to build into the numbers of their own copy.
Armor is a bonus to AC on top of 10 + Dexterity: each suit's bonus is its
standard AC less 10, and it is marked light, medium or heavy, which sets how
much Dexterity counts in it (section 5). What that cannot say (a Strength
requirement, disadvantage on Stealth) is in the note, for the DM to apply.
This list is written from the app's own data by `npm run docs:lists` (in `/backend`). Re-run it after changing the data; do not edit the list by hand.
@@ -295,20 +298,20 @@ This list is written from the app's own data by `npm run docs:lists` (in `/backe
| Item | While equipped | Note | Dice | Description |
|---|---|---|---|---|
| Hide Armor | +2 AC | Standard rules: counts at most +2 from Dexterity. | — | Thick furs and pelts. |
| Chain Shirt | +3 AC | Standard rules: counts at most +2 from Dexterity. | — | Interlocking rings worn between layers of clothing. |
| Scale Mail | +4 AC | Disadvantage on Stealth checks. Standard rules: counts at most +2 from Dexterity. | — | A coat and leggings of leather covered in overlapping metal scales. |
| Breastplate | +4 AC | Standard rules: counts at most +2 from Dexterity. | — | A fitted metal chest piece worn with supple leather. |
| Half Plate | +5 AC | Disadvantage on Stealth checks. Standard rules: counts at most +2 from Dexterity. | — | Shaped metal plates covering most of the body. |
| Hide Armor | +2 AC | Medium armor: at most +2 from Dexterity counts (worked out on the sheet). | — | Thick furs and pelts. |
| Chain Shirt | +3 AC | Medium armor: at most +2 from Dexterity counts (worked out on the sheet). | — | Interlocking rings worn between layers of clothing. |
| Scale Mail | +4 AC | Disadvantage on Stealth checks. Medium armor: at most +2 from Dexterity counts (worked out on the sheet). | — | A coat and leggings of leather covered in overlapping metal scales. |
| Breastplate | +4 AC | Medium armor: at most +2 from Dexterity counts (worked out on the sheet). | — | A fitted metal chest piece worn with supple leather. |
| Half Plate | +5 AC | Disadvantage on Stealth checks. Medium armor: at most +2 from Dexterity counts (worked out on the sheet). | — | Shaped metal plates covering most of the body. |
### Heavy Armor (4)
| Item | While equipped | Note | Dice | Description |
|---|---|---|---|---|
| Ring Mail | +4 AC | Disadvantage on Stealth checks. Standard rules: adds no Dexterity. | — | Leather armor with heavy rings sewn into it. |
| Chain Mail | +6 AC, −10 Speed | Disadvantage on Stealth checks. Standard rules: adds no Dexterity, and needs Strength 13. | — | Interlocking metal rings over a layer of quilted fabric, with gauntlets. |
| Splint Armor | +7 AC, −10 Speed | Disadvantage on Stealth checks. Standard rules: adds no Dexterity, and needs Strength 15. | — | Narrow vertical strips of metal riveted to a leather backing. |
| Plate Armor | +8 AC, −10 Speed | Disadvantage on Stealth checks. Standard rules: adds no Dexterity, and needs Strength 15. | — | Shaped, interlocking metal plates covering the whole body. |
| Ring Mail | +4 AC | Disadvantage on Stealth checks. Heavy armor: Dexterity does not count (worked out on the sheet). | — | Leather armor with heavy rings sewn into it. |
| Chain Mail | +6 AC, −10 Speed | Disadvantage on Stealth checks. Heavy armor: Dexterity does not count (worked out on the sheet). Standard rules: needs Strength 13. | — | Interlocking metal rings over a layer of quilted fabric, with gauntlets. |
| Splint Armor | +7 AC, −10 Speed | Disadvantage on Stealth checks. Heavy armor: Dexterity does not count (worked out on the sheet). Standard rules: needs Strength 15. | — | Narrow vertical strips of metal riveted to a leather backing. |
| Plate Armor | +8 AC, −10 Speed | Disadvantage on Stealth checks. Heavy armor: Dexterity does not count (worked out on the sheet). Standard rules: needs Strength 15. | — | Shaped, interlocking metal plates covering the whole body. |
### Shields (2)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "1.30.0",
"version": "1.31.0",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -16,7 +16,7 @@ const STAT_HELP: Record<string, string> = {
SKILL_BONUS: 'Skill Bonus — added when you roll to use a skill. The governing ability modifier, plus your proficiency bonus if you are proficient (doubled if you have expertise).',
EXPERTISE: 'Expertise — doubles your proficiency bonus for this skill, on top of being proficient.',
ABILITY_MODIFIER: 'Ability Modifier — the number added to rolls that use this ability. floor((score − 10) / 2).',
ARMOR_BONUS: 'Extra AC — a number the DM sets, added to AC on top of 10 + Dex modifier and whatever equipped gear gives. Armor itself is an equipped item. Unarmored Defense (barbarian, monk) and a draconic sorcerer’s scales are worked out for you: do not add them here.',
ARMOR_BONUS: 'Extra AC — a number the DM sets, added to AC on top of 10 + Dex modifier and whatever equipped gear gives. Armor itself is an equipped item, and medium or heavy armor holds back how much Dexterity counts. Unarmored Defense (barbarian, monk) and a draconic sorcerer’s scales are worked out for you: do not add them here.',
SPEED: 'Speed — how far you can move on your turn, in feet. Set by your species, unless overridden.',
SPELL_SAVE_DC: 'Spell Save DC — the difficulty a target must beat to resist your spell. 8 + proficiency bonus + your spellcasting ability modifier.',
SPELL_ATTACK_BONUS: 'Spell Attack Bonus — added when you make a spell attack roll. Proficiency bonus + your spellcasting ability modifier.',
@@ -104,7 +104,9 @@ function GearDetail({ campaignId, pool, entry, added, onClose }: { added: ReactN
)}
</Field>
<p className="combatant-sub mt-sm">
Its AC is still set below, as +AC. This says what kind it is, for the class features that depend on what is worn: a barbarian’s and a
Its AC is still set below, as +AC (its usual AC less 10). This says what kind it is. The sheet counts all of a character’s
Dexterity toward AC in light armor, at most +2 in medium armor and none in heavy armor. It also decides the class features
that depend on what is worn: a barbarian’s and a
monk’s Unarmored Defense and a draconic sorcerer’s scales stop while armor is equipped, a monk’s also behind a shield, and a
barbarian’s extra speed stops in heavy armor.
</p>
+4 -4
View File
@@ -1,19 +1,19 @@
{
"name": "dnd-campaign-manager",
"version": "1.30.0",
"version": "1.31.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dnd-campaign-manager",
"version": "1.30.0",
"version": "1.31.0",
"workspaces": [
"backend",
"frontend"
]
},
"backend": {
"version": "1.30.0",
"version": "1.31.0",
"dependencies": {
"@prisma/adapter-pg": "^7.10.0",
"@prisma/client": "^7.10.0",
@@ -33,7 +33,7 @@
}
},
"frontend": {
"version": "1.30.0",
"version": "1.31.0",
"dependencies": {
"@tanstack/react-query": "^5.103.2",
"axios": "^1.20.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dnd-campaign-manager",
"version": "1.30.0",
"version": "1.31.0",
"private": true,
"description": "D&D 5e campaign manager — monorepo root",
"workspaces": [