diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a51fb2..11589cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,17 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [1.5.1] - 2026-10-22 ### Added -- Weapons have a wound kind (`system.weapon.woundKind`: tnąca / kłująca / obuchowa, empty = unspecified), editable on the gear sheet. -- Gear parser reads `wound` / `rana` frontmatter from vault weapon notes. Missing or unknown values stay unspecified; nothing is guessed. +- Weapons have wound kinds (`system.weapon.woundKinds`: `slashing` sieczne / `piercing` kłute / `blunt` obuchowe, several allowed, empty = unspecified), editable on the gear sheet as a multi-select. Matches Foundry Sync Brief §18. +- Gear parser reads `wound` (string, comma list or YAML list; Polish or English words) from vault weapon notes. Unknown values are dropped; nothing is guessed. +- Packs rebuilt from the current vault: Sztylet piercing+slashing, Bicz slashing, Laska blunt, bows and Włócznia piercing; generic one-/two-handed entries list all three (pick the real type per weapon). + +### Changed +- `applyDamage` takes `woundKinds` (array) instead of a single kind; passive Krew applies when any is slashing or piercing. Blunt (and unarmed) never gives Krew. +- Vault sync of 2026-10-22 needs no further logic changes: runic armour wording, Zakazana Osłona retaliation, Rozerwanie Run artifact destruction and free Immediate Action for Kradzież Życia / Złożenie w Ofierze were already covered in 1.5.0 (spell, talent and discipline text arrives through the rebuilt packs). ### Notes -- Only cutting and piercing weapons feed passive Krew; unspecified weapons give none. Callers of `applyDamage` still need to pass the weapon's `woundKind`. +- Callers of `applyDamage` still need to pass the equipped weapon's `woundKinds`. +- Kości Losu use is now an Immediate Action that counts toward the limit (vault `Zużywanie Ładunków`); the system has no Fate-dice action code yet. ## [1.5.0] - 2026-10-20 diff --git a/lang/en.json b/lang/en.json index 2e04e62..c74db31 100644 --- a/lang/en.json +++ b/lang/en.json @@ -239,10 +239,10 @@ "physical": "Physical", "magical": "Magical", "environmental": "Environmental", - "woundKind": "Wound kind", + "woundKinds": "Wound kind", + "woundKindsHint": "Several allowed. Only slashing and piercing wounds grant talent Krew (Respect for Life, Crimson Duelist). Empty = unspecified.", "wound": { - "none": "Unspecified", - "cutting": "Cutting", + "slashing": "Slashing", "piercing": "Piercing", "blunt": "Blunt" }, diff --git a/lang/pl.json b/lang/pl.json index fd7f65e..b222e9e 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -259,12 +259,12 @@ "physical": "Fizyczne", "magical": "Magiczne", "environmental": "Środowiskowe", - "woundKind": "Rodzaj rany", + "woundKinds": "Rodzaj rany", + "woundKindsHint": "Można wybrać kilka. Tylko rany sieczne i kłute dają Krew z talentów (Szacunek do Życia, Krwisty Pojedynkowicz). Puste = nieokreślony.", "wound": { - "none": "Nieokreślony", - "cutting": "Tnąca", - "piercing": "Kłująca", - "blunt": "Obuchowa" + "slashing": "Sieczne", + "piercing": "Kłute", + "blunt": "Obuchowe" }, "properties": "Właściwości", "damageReduction": "Redukcja Obrażeń", diff --git a/scripts/parsers/gear-parser.ts b/scripts/parsers/gear-parser.ts index 96b4c6b..7cfecfd 100644 --- a/scripts/parsers/gear-parser.ts +++ b/scripts/parsers/gear-parser.ts @@ -38,13 +38,25 @@ export const parseGear: ParserFn = async (ctx: ParserContext): Promise = { + slashing: 'slashing', sieczne: 'slashing', sieczna: 'slashing', + piercing: 'piercing', kłute: 'piercing', kłuta: 'piercing', kluta: 'piercing', kłujące: 'piercing', + blunt: 'blunt', obuchowe: 'blunt', obuchowa: 'blunt', + }; + const out: WoundKind[] = []; + for (const part of (raw ?? '').split(',')) { + const kind = map[part.trim().toLowerCase()]; + if (kind && !out.includes(kind)) out.push(kind); + } + return out; } function extractMarkdown(raw: string): { frontmatter: Record; content: string } { @@ -57,9 +69,17 @@ function extractMarkdown(raw: string): { frontmatter: Record; co const fmText = raw.slice(3, endIdx).trim(); content = raw.slice(endIdx + 3).trim(); - const lines = fmText.split('\n'); + const lines = fmText.split(/\r?\n/); + let listKey = ''; for (const line of lines) { + // "key:" followed by " - item" lines → comma-joined value. + const item = line.match(/^\s+-\s+(.*)$/); + if (item && listKey) { + fm[listKey] = fm[listKey] ? `${fm[listKey]}, ${item[1].trim()}` : item[1].trim(); + continue; + } const colonIdx = line.indexOf(':'); + listKey = colonIdx > -1 && !line.slice(colonIdx + 1).trim() ? line.slice(0, colonIdx).trim() : ''; if (colonIdx > -1) { const key = line.slice(0, colonIdx).trim(); let val = line.slice(colonIdx + 1).trim(); @@ -100,7 +120,7 @@ function buildGearDoc(name: string, fm: Record, content: string, weapon: { damage: '', damageType: 'physical', - woundKind: '', + woundKinds: [], properties: [], }, armor: { @@ -115,7 +135,7 @@ function buildGearDoc(name: string, fm: Record, content: string, if (category === 'weapon') { system.weapon.damage = fm.damage || fm.obrazenia || ''; - system.weapon.woundKind = parseWoundKind(fm.wound || fm.rana); + system.weapon.woundKinds = parseWoundKinds(fm.wound || fm.rana); if (fm.traits || fm.cechy) { system.weapon.properties = parseList(fm.traits || fm.cechy); } diff --git a/src/constants.ts b/src/constants.ts index 93b3ae3..aea5abc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -192,8 +192,9 @@ export type GearCategory = (typeof GEAR_CATEGORIES)[number]; export const DAMAGE_TYPES = ['physical', 'magical', 'environmental'] as const; export type DamageType = (typeof DAMAGE_TYPES)[number]; -/** Weapon wound kinds ('' = unspecified). Cutting / piercing feed the passive Krew talents. */ -export const WOUND_KINDS = ['', 'cutting', 'piercing', 'blunt'] as const; +/** Weapon wound kinds (sieczne / kłute / obuchowe). A weapon may have several; none = unspecified. + * Only slashing and piercing wounds feed the passive Krew talents. */ +export const WOUND_KINDS = ['slashing', 'piercing', 'blunt'] as const; export type WoundKind = (typeof WOUND_KINDS)[number]; /** Armor types */ diff --git a/src/data/item-gear.ts b/src/data/item-gear.ts index a501ae9..980df3b 100644 --- a/src/data/item-gear.ts +++ b/src/data/item-gear.ts @@ -39,8 +39,8 @@ export class GearData extends foundry.abstract.TypeDataModel { choices: DAMAGE_TYPES as unknown as readonly string[], }), properties: makeArrayField(new f.StringField({ blank: false })), - // Rodzaj rany: cutting (tnąca) / piercing (kłująca) / blunt (obuchowa); '' = not specified. - woundKind: makeStringField('', { blank: true, choices: WOUND_KINDS as unknown as readonly string[] }), + // Rodzaj rany: slashing (sieczne) / piercing (kłute) / blunt (obuchowe); several allowed, empty = unspecified. + woundKinds: makeArrayField(new f.StringField({ blank: false, choices: WOUND_KINDS as unknown as string[] })), }), armor: new f.SchemaField({ diff --git a/src/logic/blood-magic.ts b/src/logic/blood-magic.ts index 50c2520..58870c3 100644 --- a/src/logic/blood-magic.ts +++ b/src/logic/blood-magic.ts @@ -180,7 +180,7 @@ export async function resolveLifeSteal(actor: CastableActor, hpActuallyLost: num /** * Złożenie w Ofierze: 1 Zapał, free Akcja Natychmiastowa, once per round. Triggers when your attack - * kills a target with a cutting / piercing wound within 1 m. `successes` are the Sukcesy of the + * kills a target with a slashing / piercing wound within 1 m. `successes` are the Sukcesy of the * killing attack's Test: heal Sukcesy + Magia, gain Krew = Sukcesy. */ export async function sacrifice(actor: CastableActor, successes: number): Promise { @@ -256,7 +256,7 @@ export async function absorbBlood( /** * Passive Krew (talents): 1 per 4 HP lost (Szacunek do Życia) / 1 per 2 HP (Krwisty Pojedynkowicz), - * by either side, only from cutting or piercing weapons. Call for the victim and the attacker. + * by either side, only from slashing or piercing weapons. Call for the victim and the attacker. */ export async function gainKrewFromWeaponDamage(actor: CastableActor, hpLost: number): Promise { const blood = (actor.system.attributes as any)?.blood; diff --git a/src/logic/damage.ts b/src/logic/damage.ts index 83efb8a..6fa8344 100644 --- a/src/logic/damage.ts +++ b/src/logic/damage.ts @@ -35,7 +35,8 @@ export interface ApplyDamageOptions { /** Rozerwanie Run: permanently destroys N Runiczny Pancerz points (lowers its maximum). */ destroyRunicPoints?: number; /** Wound kind of a weapon hit (gear data has none yet – callers pass it); drives passive Krew. */ - woundKind?: 'cutting' | 'piercing' | 'blunt'; + /** Wound kinds of the attacking weapon; only slashing / piercing give passive Krew. */ + woundKinds?: ('slashing' | 'piercing' | 'blunt')[]; /** Attacker, for passive Krew gain (Szacunek do Życia / Krwisty Pojedynkowicz). */ sourceActor?: any; postChat?: boolean; @@ -161,8 +162,8 @@ export async function applyDamage(actor: ActorLike, opts: ApplyDamageOptions): P if (Object.keys(updates).length > 0) await actor.update(updates); - // Passive Krew from cutting / piercing weapons (talents): victim and attacker side. - if (report.hpDamage > 0 && (opts.woundKind === 'cutting' || opts.woundKind === 'piercing')) { + // Passive Krew from slashing / piercing weapons (talents): victim and attacker side. + if (report.hpDamage > 0 && (opts.woundKinds ?? []).some((k) => k === 'slashing' || k === 'piercing')) { report.krewGained = { victim: await gainKrewFromWeaponDamage(actor as any, report.hpDamage), source: opts.sourceActor ? await gainKrewFromWeaponDamage(opts.sourceActor, report.hpDamage) : 0, diff --git a/src/logic/rules.ts b/src/logic/rules.ts index c5ffcdb..879a3a6 100644 --- a/src/logic/rules.ts +++ b/src/logic/rules.ts @@ -196,7 +196,7 @@ export function greatSacrificeResult(successes: number, highestAttribute: number if (successes > highestAttribute) return { kills: true, heal: 0, krew: Math.max(0, targetHealth) }; return { kills: false, heal: Math.max(0, successes), krew: 0 }; } -/** Passive Krew: Szacunek do Życia 1 per 4 HP, Krwisty Pojedynkowicz 1 per 2 HP (cutting/piercing only). */ +/** Passive Krew: Szacunek do Życia 1 per 4 HP, Krwisty Pojedynkowicz 1 per 2 HP (slashing/piercing only). */ export function krewFromWeaponHpLoss(hpLost: number, talent: KrewTalent | null): number { if (talent === 'respect-for-life') return Math.floor(Math.max(0, hpLost) / 4); if (talent === 'krwisty-pojedynkowicz') return Math.floor(Math.max(0, hpLost) / 2); diff --git a/src/sheets/item-sheet.ts b/src/sheets/item-sheet.ts index 6f54b2b..db93e9f 100644 --- a/src/sheets/item-sheet.ts +++ b/src/sheets/item-sheet.ts @@ -9,6 +9,7 @@ import { AOE_SHAPES, SPELL_DAMAGE_TYPES, TRIGGER_EVENTS, + WOUND_KINDS, } from '../constants'; const { ItemSheetV2 } = foundry.applications.sheets as unknown as { @@ -67,6 +68,7 @@ export class HbmItemSheet extends HandlebarsApplicationMixin(ItemSheetV2) { itemType: item.type, bodyPartial: TYPE_PARTIAL[item.type] ?? 'systems/hbm-rpg-v3/templates/item/_unknown.hbs', choices: buildChoiceMaps(), + woundChoices: Object.fromEntries(WOUND_KINDS.map((k) => [k, `HBM.gear.wound.${k}`])), effects, supportsEffects, }; diff --git a/templates/item/gear.hbs b/templates/item/gear.hbs index aeae6e1..bdf58a4 100644 --- a/templates/item/gear.hbs +++ b/templates/item/gear.hbs @@ -31,13 +31,10 @@ -