- Weapons and items can carry effects: bonuses and penalties to AC, speed, initiative, max HP, passive Perception, ability scores, saving throws and skills, plus a note. They apply on their own while equipped and stop when the thing comes off. Armor is an item with an AC effect. The DM sets them with "Effects" on a row in the weapon or item pool. - The sheet's "armor bonus" is now "Extra AC", the DM's own adjustment on top of gear, and only the DM can set it. - Only what is equipped counts: a weapon has to be equipped to be used in an encounter. A character can have two things equipped at once, one of them a weapon (campaign settings). Equipping at the limit is refused with a message, and can be changed at any time, mid-encounter included. - A built-in item library: armor, shields, worn gear and supplies, with their effects set. The weapon library gains paired weapons (a pair of daggers is one weapon). - A floating character button on every page opens a panel with a character's numbers and everything they hold, with equip ticks. Players get their own characters, the DM everyone's, and each character card in an encounter has an Inventory button. It replaces the quick stats drawer. - A player's own characters stand out: marked and highlighted in encounters, with a button that jumps to their card; highlighted on the party screen; and listed once, not twice, on the campaign page. - All packages at version 1.11.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
import { ABILITY_LABELS, SKILL_LABELS, fmtMod } from './format';
|
||
import type { GearEffect } from '../api/types';
|
||
|
||
// What a weapon or item can change on the sheet of whoever has it equipped.
|
||
// Mirrors GEAR_STATS in backend/src/game/calculations.js; the server is what
|
||
// applies them, this is only for naming them and offering them in a form.
|
||
const ABILITIES = Object.keys(ABILITY_LABELS);
|
||
const SKILLS = Object.keys(SKILL_LABELS);
|
||
|
||
export const GEAR_STAT_GROUPS: { label: string; stats: string[] }[] = [
|
||
{ label: 'Combat', stats: ['AC', 'SPEED', 'INITIATIVE', 'MAX_HP', 'PASSIVE_PERCEPTION'] },
|
||
{ label: 'Ability scores', stats: ABILITIES },
|
||
{ label: 'Saving throws', stats: ['SAVES', ...ABILITIES.map((a) => `SAVE_${a}`)] },
|
||
{ label: 'Skills', stats: ['SKILLS', ...SKILLS.map((s) => `SKILL_${s}`)] },
|
||
];
|
||
|
||
const FIXED_LABELS: Record<string, string> = {
|
||
AC: 'AC',
|
||
SPEED: 'Speed',
|
||
INITIATIVE: 'Initiative',
|
||
MAX_HP: 'Max HP',
|
||
PASSIVE_PERCEPTION: 'Passive Perception',
|
||
SAVES: 'All saving throws',
|
||
SKILLS: 'All skill checks',
|
||
};
|
||
|
||
export function gearStatLabel(stat: string): string {
|
||
if (FIXED_LABELS[stat]) return FIXED_LABELS[stat];
|
||
if (stat.startsWith('SAVE_')) return `${ABILITY_LABELS[stat.slice(5)] ?? stat} saves`;
|
||
if (stat.startsWith('SKILL_')) return SKILL_LABELS[stat.slice(6)] ?? stat;
|
||
return ABILITY_LABELS[stat] ?? stat;
|
||
}
|
||
|
||
// "+6 AC", "−10 Speed".
|
||
export function effectLabel(effect: GearEffect): string {
|
||
return `${fmtMod(effect.amount).replace('-', '−')} ${gearStatLabel(effect.stat)}`;
|
||
}
|
||
|
||
// "+6 AC · −10 Speed · Disadvantage on Stealth", or null when it does nothing.
|
||
export function effectsSummary(effects: GearEffect[] | undefined, note?: string | null): string | null {
|
||
const parts = [...(effects ?? []).map(effectLabel), ...(note ? [note] : [])];
|
||
return parts.length ? parts.join(' · ') : null;
|
||
}
|