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:
@@ -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,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
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.'),
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "1.30.0",
|
||||
"version": "1.31.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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>
|
||||
|
||||
Generated
+4
-4
@@ -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
@@ -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": [
|
||||
|
||||
Reference in New Issue
Block a user