feat: Foundry Sync Brief 2026-10-26 (partial: casting penalties, prayer route, Hartowany, Próg Obłędu) #11
@@ -5,6 +5,21 @@ All notable changes to the **HbM RPG v3** Foundry VTT system.
|
|||||||
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
||||||
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased] - 2026-10 sync (partial, Foundry Sync Brief 2026-10-26)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Casting penalties from armour: Średni −1 die, Ciężki −3 dice and +2 Mana (also overcast), Tarcza −1 die (stacks); Pancerz Wiary removes armour but not shield penalties. Gear flag `armor.ignoresCastingPenalty` for special-case armour.
|
||||||
|
- Every spell, sacred ones included, can be cast as a regular spell or as a prayer (cast dialog "Sposób rzucenia"). Prayer = Dusza + Oddanie, Mana + 1 Zapał; sacred spells default to prayer.
|
||||||
|
- `damageCategory` spell flag: +1 damage for category Obrażenia (off by default; vault frontmatter has no category yet).
|
||||||
|
- `runic` spell school (alias of the `runes` discipline for the discipline gate).
|
||||||
|
- Pure helpers in `rules.ts`: `golemLimit`, `fallDamage`, `survivalTest`, `zealRegenBlocked`, `madnessThreshold` (not all wired yet).
|
||||||
|
- Characters have `mentalIllnesses`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Wiedźmia Magia: Poziom Skomplikowania = number of Symbols; `complexityLevel` removed from spells.
|
||||||
|
- Hartowany w Ogniu: flat −2 per fire hit (min 0) instead of halving.
|
||||||
|
- Próg Obłędu = Dusza + 3 (Krasnolud +1); reaching it resets Punkty Obłędu and rolls the illness k100. Replaces the old 3/6/10/15 thresholds.
|
||||||
|
|
||||||
## [1.5.2] - 2026-10-26
|
## [1.5.2] - 2026-10-26
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -123,7 +123,12 @@ export function registerCombatHooks(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Per-turn Zeal regen (characters) - base 1 + talent bonus from flag.
|
// Per-turn Zeal regen (characters) - base 1 + talent bonus from flag.
|
||||||
|
// Blocked when since the previous turn the actor paid for an extra Action or an extra Immediate Action
|
||||||
|
// (flag `paidExtra`, set by whoever spends the Zapał; prayers / own-cost Talents never set it).
|
||||||
if (actor.type === 'character') {
|
if (actor.type === 'character') {
|
||||||
|
const blocked = Boolean(actor.getFlag?.(FLAG_SCOPE, 'paidExtra'));
|
||||||
|
if (actor.getFlag?.(FLAG_SCOPE, 'paidExtra')) await actor.unsetFlag?.(FLAG_SCOPE, 'paidExtra');
|
||||||
|
if (blocked) return;
|
||||||
const sys = actor.system as { attributes: { zeal: { value: number; max: number } } };
|
const sys = actor.system as { attributes: { zeal: { value: number; max: number } } };
|
||||||
const regen = getZealRegen(actor);
|
const regen = getZealRegen(actor);
|
||||||
const next = Math.min(sys.attributes.zeal.max, sys.attributes.zeal.value + regen);
|
const next = Math.min(sys.attributes.zeal.max, sys.attributes.zeal.value + regen);
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
castingArmorPenalty, hardenedFireDamage, madnessThreshold, golemLimit, fallDamage, survivalTest,
|
||||||
|
resolveCastAsPrayer, zealRegenBlocked,
|
||||||
|
} from '../../src/logic/rules';
|
||||||
|
|
||||||
|
describe('2026-10-26 sync rules', () => {
|
||||||
|
it('armour casting penalties stack and Pancerz Wiary spares only armour', () => {
|
||||||
|
expect(castingArmorPenalty([{ armorType: 'medium' }])).toEqual({ dice: 1, mana: 0 });
|
||||||
|
expect(castingArmorPenalty([{ armorType: 'heavy' }, { armorType: 'shield' }])).toEqual({ dice: 4, mana: 2 });
|
||||||
|
expect(castingArmorPenalty([{ armorType: 'heavy' }, { armorType: 'shield' }], true)).toEqual({ dice: 1, mana: 0 });
|
||||||
|
expect(castingArmorPenalty([{ armorType: 'medium', ignoresCastingPenalty: true }])).toEqual({ dice: 0, mana: 0 });
|
||||||
|
});
|
||||||
|
it('Hartowany w Ogniu is flat -2, min 0', () => {
|
||||||
|
expect(hardenedFireDamage(5)).toBe(3);
|
||||||
|
expect(hardenedFireDamage(1)).toBe(0);
|
||||||
|
});
|
||||||
|
it('Próg Obłędu, golem limit, fall damage, Test Przeżycia', () => {
|
||||||
|
expect(madnessThreshold(4)).toBe(7);
|
||||||
|
expect(madnessThreshold(4, 'dwarf')).toBe(8);
|
||||||
|
expect(golemLimit(5)).toBe(2);
|
||||||
|
expect(golemLimit(1)).toBe(1);
|
||||||
|
expect(fallDamage(2)).toBe(0);
|
||||||
|
expect(fallDamage(6)).toBe(4);
|
||||||
|
expect(fallDamage(6, true)).toBe(2);
|
||||||
|
expect(fallDamage(100)).toBe(20);
|
||||||
|
expect(survivalTest(2)).toEqual({ threshold: 4, successes: 1 });
|
||||||
|
expect(survivalTest(6, 1)).toEqual({ threshold: 5, successes: 3 });
|
||||||
|
});
|
||||||
|
it('sacred spells default to prayer but can be cast as regular spells', () => {
|
||||||
|
const sacred = { system: { castingMode: 'sacred' } };
|
||||||
|
expect(resolveCastAsPrayer(sacred, {})).toBe(true);
|
||||||
|
expect(resolveCastAsPrayer(sacred, { castAsPrayer: false })).toBe(false);
|
||||||
|
expect(resolveCastAsPrayer({ system: { castingMode: 'standard' } }, {})).toBe(false);
|
||||||
|
});
|
||||||
|
it('paid extra actions block Zapał regen', () => {
|
||||||
|
expect(zealRegenBlocked(0, 0)).toBe(false);
|
||||||
|
expect(zealRegenBlocked(0, 1)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,7 +19,7 @@ describe('spell-parser', () => {
|
|||||||
expect(s.damageBase).toBe('magicalAbilities/2');
|
expect(s.damageBase).toBe('magicalAbilities/2');
|
||||||
expect(s.damageType).toBe('magical');
|
expect(s.damageType).toBe('magical');
|
||||||
expect(s.difficulty).toEqual({ threshold: 4, successes: 1 });
|
expect(s.difficulty).toEqual({ threshold: 4, successes: 1 });
|
||||||
expect(s.overcastOptions).toEqual([{ description: '+1 punkt Obrażeń (najwyżej dwa Poziomy Nadczarowania)', manaPerStep: 1 }]);
|
expect(s.overcastOptions).toEqual([{ description: '+1 punkt Obrażeń', manaPerStep: 1 }]);
|
||||||
expect(s).not.toHaveProperty('circle');
|
expect(s).not.toHaveProperty('circle');
|
||||||
|
|
||||||
for (const d of docs) {
|
for (const d of docs) {
|
||||||
|
|||||||
Reference in New Issue
Block a user