Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3781a2e8 | ||
|
|
9f93745c42 |
+4
-3
@@ -5,10 +5,11 @@ 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]
|
## [1.5.2] - 2026-10-26
|
||||||
|
|
||||||
### Added
|
### Fixed
|
||||||
- Spell cast dialog has a **Modyfikator liczby kości** (dice modifier, +/-). It changes the number of dice in the casting test for every casting mode (standard, sacred, witch, blood, prayer); the pool never drops below 0. The over-limit Nadczarowanie test uses the same modified pool. API: `CastOptions.diceModifier`, `applyDiceModifier()` in `rules.ts`.
|
- A character with the discipline Magia Sakralna - Egzorcyzmy (`sacredExorcism`) is now treated as a Sacred Magic caster in the spell discipline gate. Before, it only matched its own id, so exorcists failed the requirement for Sacred spells. The match is one-way: a plain Sacred mage is not an exorcist.
|
||||||
|
- The "new round" chat message and README no longer claim that Mana is reset. Mana is a daily pool in the vault and has not been reset per round since 1.3.x; the message now says which counters really reset.
|
||||||
|
|
||||||
## [1.5.1] - 2026-10-22
|
## [1.5.1] - 2026-10-22
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ A companion lore module ([`foundry-lore`](https://github.com/iwo-strzebonski/hbm
|
|||||||
- **TS d6 dice pool roller** (`HbmTSRoll`) - `1` auto-fail, `6` auto-success, configurable Threshold (T) and required Successes (Y), plus T-step / Y-step modifiers driven by Active Effects.
|
- **TS d6 dice pool roller** (`HbmTSRoll`) - `1` auto-fail, `6` auto-success, configurable Threshold (T) and required Successes (Y), plus T-step / Y-step modifiers driven by Active Effects.
|
||||||
- **Derived stats** - `health.max = 3 × (body + mind + soul)`, `zeal.max = ceil(soul/2)`, initiative pool, mana table lookup (Magic Power Level 0–X), blood pool (`body + soul`), elixir tolerance cap (`body + 1`), insanity counter.
|
- **Derived stats** - `health.max = 3 × (body + mind + soul)`, `zeal.max = ceil(soul/2)`, initiative pool, mana table lookup (Magic Power Level 0–X), blood pool (`body + soul`), elixir tolerance cap (`body + 1`), insanity counter.
|
||||||
- **4-layer damage model** - Magical Armor (with runic counter) → Magical Shield → Physical Armor (DR + condition) → Health. `Apply Damage` dialog with bypass flags; auto-applies `Oszołomiony` on heavy HP loss.
|
- **4-layer damage model** - Magical Armor (with runic counter) → Magical Shield → Physical Armor (DR + condition) → Health. `Apply Damage` dialog with bypass flags; auto-applies `Oszołomiony` on heavy HP loss.
|
||||||
- **Combat hooks** - Mana resets each round; Zeal +1 each turn; turns auto-skipped for Nieprzytomny / Obezwładniony; Podpalony ticks 1 environmental damage; Umierający prompts a Death Save.
|
- **Combat hooks** - Mana is a daily pool (not reset per round; recovered by rests); Zeal +1 each turn; Immediate Action and Krew counters reset each round; turns auto-skipped for Nieprzytomny / Obezwładniony; Podpalony ticks 1 environmental damage; Umierający prompts a Death Save.
|
||||||
- **Spell-cast workflow** - `validateCast()` gates on resources / race / talent / discipline / deity / group / inCombat / superspell / witch-symbol; dispatcher routes to standard / Sacred / Witch / Blood / Hekate hybrid; `rollSpellDamage()` parses formula tokens; reactive triggers (`killWithWeapon`, `targetCastsSpell`, `damageTaken`, `turnStart`).
|
- **Spell-cast workflow** - `validateCast()` gates on resources / race / talent / discipline / deity / group / inCombat / superspell / witch-symbol; dispatcher routes to standard / Sacred / Witch / Blood / Hekate hybrid; `rollSpellDamage()` parses formula tokens; reactive triggers (`killWithWeapon`, `targetCastsSpell`, `damageTaken`, `turnStart`).
|
||||||
- **Cast dialog** - mana / zeal / blood inputs, group-caster picker, Hekate toggle, GM bypass checkboxes (superspell, non-combat block), inline validation feedback.
|
- **Cast dialog** - mana / zeal / blood inputs, group-caster picker, Hekate toggle, GM bypass checkboxes (superspell, non-combat block), inline validation feedback.
|
||||||
- **Mechanics modules** (`game.hbm.*`) - `bloodMagic` (Self-Harm, Life Steal, talent integration), `abyssMagic` (Aspects vs Primal, Mistrz Losu penalty, `Dary Otchłani`, insanity gain), `brewing` (TS test + tolerance counter), `rest` (short / long), `trade` (founding company workflow, transactions, smuggling).
|
- **Mechanics modules** (`game.hbm.*`) - `bloodMagic` (Self-Harm, Life Steal, talent integration), `abyssMagic` (Aspects vs Primal, Mistrz Losu penalty, `Dary Otchłani`, insanity gain), `brewing` (TS test + tolerance counter), `rest` (short / long), `trade` (founding company workflow, transactions, smuggling).
|
||||||
|
|||||||
+1
-3
@@ -363,8 +363,6 @@
|
|||||||
},
|
},
|
||||||
"spellCast": {
|
"spellCast": {
|
||||||
"title": "Casting spell",
|
"title": "Casting spell",
|
||||||
"diceModifier": "Dice modifier",
|
|
||||||
"diceModifierHint": "+/- dice in the casting test (the pool never drops below 0)",
|
|
||||||
"manaSpent": "Mana spent",
|
"manaSpent": "Mana spent",
|
||||||
"cannotBeUnraveled": "Cannot be unraveled",
|
"cannotBeUnraveled": "Cannot be unraveled",
|
||||||
"zealSpent": "Zeal spent",
|
"zealSpent": "Zeal spent",
|
||||||
@@ -425,7 +423,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"combat": {
|
"combat": {
|
||||||
"newRound": "New round - Mana reset",
|
"newRound": "New round - Immediate Action and Krew counters reset",
|
||||||
"newTurn": "New turn - Zeal +1",
|
"newTurn": "New turn - Zeal +1",
|
||||||
"turnSkipped": "Turn skipped (condition)",
|
"turnSkipped": "Turn skipped (condition)",
|
||||||
"deathSavePrompt": "Make a Death Saving Throw (Body, T:4, Y:1)"
|
"deathSavePrompt": "Make a Death Saving Throw (Body, T:4, Y:1)"
|
||||||
|
|||||||
+1
-3
@@ -383,8 +383,6 @@
|
|||||||
},
|
},
|
||||||
"spellCast": {
|
"spellCast": {
|
||||||
"title": "Rzucanie zaklęcia",
|
"title": "Rzucanie zaklęcia",
|
||||||
"diceModifier": "Modyfikator liczby kości",
|
|
||||||
"diceModifierHint": "+/- kości w Teście rzucania (liczba kości nie spadnie poniżej 0)",
|
|
||||||
"manaSpent": "Wydana mana",
|
"manaSpent": "Wydana mana",
|
||||||
"cannotBeUnraveled": "Nie da się rozpleść",
|
"cannotBeUnraveled": "Nie da się rozpleść",
|
||||||
"cast": "Rzuć",
|
"cast": "Rzuć",
|
||||||
@@ -428,7 +426,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"combat": {
|
"combat": {
|
||||||
"newRound": "Nowa runda - Mana zresetowana",
|
"newRound": "Nowa runda - liczniki Akcji Natychmiastowych i Krwi zresetowane",
|
||||||
"newTurn": "Nowa tura - Zapał +1",
|
"newTurn": "Nowa tura - Zapał +1",
|
||||||
"turnSkipped": "Tura pominięta (stan)",
|
"turnSkipped": "Tura pominięta (stan)",
|
||||||
"deathSavePrompt": "Wykonaj Rzut Obronny przeciw Śmierci (Ciało, T:4, Y:1)"
|
"deathSavePrompt": "Wykonaj Rzut Obronny przeciw Śmierci (Ciało, T:4, Y:1)"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hbm-rpg-v3-foundry",
|
"name": "hbm-rpg-v3-foundry",
|
||||||
"version": "1.5.1",
|
"version": "1.5.2",
|
||||||
"description": "Foundry VTT system for Homebrew Magic: Role Playing Game v3",
|
"description": "Foundry VTT system for Homebrew Magic: Role Playing Game v3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -76,12 +76,6 @@ export async function askCastOptions(spell: SpellForDialog, actor: ActorForDialo
|
|||||||
<input type="number" name="bloodSpent" value="${baseBlood}" min="${baseBlood}" max="${max}"/>
|
<input type="number" name="bloodSpent" value="${baseBlood}" min="${baseBlood}" max="${max}"/>
|
||||||
</div>`);
|
</div>`);
|
||||||
}
|
}
|
||||||
fields.push(`
|
|
||||||
<div class="form-group">
|
|
||||||
<label>${game.i18n.localize('HBM.spellCast.diceModifier')}</label>
|
|
||||||
<input type="number" name="diceModifier" value="0" step="1"/>
|
|
||||||
<small>${game.i18n.localize('HBM.spellCast.diceModifierHint')}</small>
|
|
||||||
</div>`);
|
|
||||||
if (mode !== 'sacred') {
|
if (mode !== 'sacred') {
|
||||||
fields.push(`
|
fields.push(`
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -168,8 +162,6 @@ export async function askCastOptions(spell: SpellForDialog, actor: ActorForDialo
|
|||||||
}
|
}
|
||||||
if (fd.zealSpent != null) opts.zealSpent = Math.max(1, Number(fd.zealSpent) || 1);
|
if (fd.zealSpent != null) opts.zealSpent = Math.max(1, Number(fd.zealSpent) || 1);
|
||||||
if (fd.bloodSpent != null) opts.bloodSpent = Math.max(baseBlood, Number(fd.bloodSpent) || baseBlood);
|
if (fd.bloodSpent != null) opts.bloodSpent = Math.max(baseBlood, Number(fd.bloodSpent) || baseBlood);
|
||||||
const diceMod = Math.trunc(Number(fd.diceModifier) || 0);
|
|
||||||
if (diceMod !== 0) opts.diceModifier = diceMod;
|
|
||||||
if (fd.hekateMode) opts.hekateMode = 'witch';
|
if (fd.hekateMode) opts.hekateMode = 'witch';
|
||||||
if (fd.castAsPrayer) opts.castAsPrayer = true;
|
if (fd.castAsPrayer) opts.castAsPrayer = true;
|
||||||
if (fd.bypassSuperspellWarning) opts.bypassSuperspellWarning = true;
|
if (fd.bypassSuperspellWarning) opts.bypassSuperspellWarning = true;
|
||||||
|
|||||||
@@ -259,8 +259,3 @@ export function negotiationBand(successes: number): number {
|
|||||||
export function fearThresholdMod(sourceId: string | undefined, targetId: string | undefined): number {
|
export function fearThresholdMod(sourceId: string | undefined, targetId: string | undefined): number {
|
||||||
return sourceId && targetId && sourceId === targetId ? 1 : 0;
|
return sourceId && targetId && sourceId === targetId ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Casting test pool with the player's +/- dice modifier (whole dice, never below 0). */
|
|
||||||
export function applyDiceModifier(pool: number, modifier?: number): number {
|
|
||||||
return Math.max(0, Math.trunc(Number(pool) || 0) + Math.trunc(Number(modifier) || 0));
|
|
||||||
}
|
|
||||||
|
|||||||
+5
-10
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { HbmTSRoll } from '../dice/ts-roll';
|
import { HbmTSRoll } from '../dice/ts-roll';
|
||||||
import { TS_DEFAULT_REQUIRED, TS_DEFAULT_THRESHOLD, getMagicPowerEntry } from '../constants';
|
import { TS_DEFAULT_REQUIRED, TS_DEFAULT_THRESHOLD, getMagicPowerEntry } from '../constants';
|
||||||
import { manaToBorrow, exhaustionDice, manaExhaustionTier, overLimitTest, applyDiceModifier, SPELL_DAMAGE_PROFILES } from './rules';
|
import { manaToBorrow, exhaustionDice, manaExhaustionTier, overLimitTest, SPELL_DAMAGE_PROFILES } from './rules';
|
||||||
import { validateCast } from './spell-validation';
|
import { validateCast } from './spell-validation';
|
||||||
import type { ValidationResult } from './spell-validation';
|
import type { ValidationResult } from './spell-validation';
|
||||||
import { rollSpellDamage } from './spell-damage';
|
import { rollSpellDamage } from './spell-damage';
|
||||||
@@ -76,8 +76,6 @@ export interface CastOptions {
|
|||||||
bypassSuperspellWarning?: boolean;
|
bypassSuperspellWarning?: boolean;
|
||||||
bypassNonCombatBlock?: boolean;
|
bypassNonCombatBlock?: boolean;
|
||||||
groupCasters?: string[];
|
groupCasters?: string[];
|
||||||
/** Modifier to the number of dice in the casting test (+/-, pool never below 0). */
|
|
||||||
diceModifier?: number;
|
|
||||||
/** Override required successes (used by variableSuccesses summons). */
|
/** Override required successes (used by variableSuccesses summons). */
|
||||||
requiredOverride?: number;
|
requiredOverride?: number;
|
||||||
speaker?: ChatMessage.SpeakerData;
|
speaker?: ChatMessage.SpeakerData;
|
||||||
@@ -278,10 +276,9 @@ async function castStandard(actor: CastableActor, spell: SpellLike, opts: CastOp
|
|||||||
const magicDice = typeof a.magic.dicePool === 'number'
|
const magicDice = typeof a.magic.dicePool === 'number'
|
||||||
? a.magic.dicePool
|
? a.magic.dicePool
|
||||||
: getMagicPowerEntry(a.magic.actual ?? a.magic.value ?? 0).dicePool;
|
: getMagicPowerEntry(a.magic.actual ?? a.magic.value ?? 0).dicePool;
|
||||||
const basePool = opts.castAsPrayer
|
const pool = opts.castAsPrayer
|
||||||
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
|
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
|
||||||
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
|
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
|
||||||
const pool = applyDiceModifier(basePool, opts.diceModifier);
|
|
||||||
const flavor = opts.castAsPrayer
|
const flavor = opts.castAsPrayer
|
||||||
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')}`
|
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')}`
|
||||||
: `${spell.name}`;
|
: `${spell.name}`;
|
||||||
@@ -352,7 +349,7 @@ async function castSacred(actor: CastableActor, spell: SpellLike, opts: CastOpti
|
|||||||
const zealCost = Math.max(1, opts.zealSpent ?? 1);
|
const zealCost = Math.max(1, opts.zealSpent ?? 1);
|
||||||
await actor.update({ 'system.attributes.zeal.value': a.zeal.value - zealCost });
|
await actor.update({ 'system.attributes.zeal.value': a.zeal.value - zealCost });
|
||||||
|
|
||||||
const pool = applyDiceModifier(a.soul.value + (actor.system.skills?.devotion?.value ?? 0), opts.diceModifier);
|
const pool = a.soul.value + (actor.system.skills?.devotion?.value ?? 0);
|
||||||
const flavor = `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.sacred')}`;
|
const flavor = `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.sacred')}`;
|
||||||
const required = opts.requiredOverride ?? spell.system.difficulty.successes ?? TS_DEFAULT_REQUIRED;
|
const required = opts.requiredOverride ?? spell.system.difficulty.successes ?? TS_DEFAULT_REQUIRED;
|
||||||
const roll = HbmTSRoll.fromParams({
|
const roll = HbmTSRoll.fromParams({
|
||||||
@@ -382,10 +379,9 @@ async function castWitch(actor: CastableActor, spell: SpellLike, opts: CastOptio
|
|||||||
const magicDice = typeof (a.magic as any).dicePool === 'number'
|
const magicDice = typeof (a.magic as any).dicePool === 'number'
|
||||||
? (a.magic as any).dicePool
|
? (a.magic as any).dicePool
|
||||||
: getMagicPowerEntry((a.magic as any).actual ?? a.magic.value ?? 0).dicePool;
|
: getMagicPowerEntry((a.magic as any).actual ?? a.magic.value ?? 0).dicePool;
|
||||||
const basePool = opts.castAsPrayer
|
const pool = opts.castAsPrayer
|
||||||
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
|
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
|
||||||
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
|
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
|
||||||
const pool = applyDiceModifier(basePool, opts.diceModifier);
|
|
||||||
const flavor = opts.castAsPrayer
|
const flavor = opts.castAsPrayer
|
||||||
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')} (${required}/${maxSymbols})`
|
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')} (${required}/${maxSymbols})`
|
||||||
: `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.witch')} (${required}/${maxSymbols})`;
|
: `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.witch')} (${required}/${maxSymbols})`;
|
||||||
@@ -425,10 +421,9 @@ async function castBlood(actor: CastableActor, spell: SpellLike, opts: CastOptio
|
|||||||
const magicDice = typeof (a.magic as any).dicePool === 'number'
|
const magicDice = typeof (a.magic as any).dicePool === 'number'
|
||||||
? (a.magic as any).dicePool
|
? (a.magic as any).dicePool
|
||||||
: getMagicPowerEntry((a.magic as any).actual ?? a.magic.value ?? 0).dicePool;
|
: getMagicPowerEntry((a.magic as any).actual ?? a.magic.value ?? 0).dicePool;
|
||||||
const basePool = opts.castAsPrayer
|
const pool = opts.castAsPrayer
|
||||||
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
|
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
|
||||||
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
|
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
|
||||||
const pool = applyDiceModifier(basePool, opts.diceModifier);
|
|
||||||
const flavor = opts.castAsPrayer
|
const flavor = opts.castAsPrayer
|
||||||
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')}`
|
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')}`
|
||||||
: `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.blood')}`;
|
: `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.blood')}`;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function actorHasTalent(actor: CastableActor, talentId: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchDiscipline(disciplineId: string, targetId: string): boolean {
|
export function matchDiscipline(disciplineId: string, targetId: string): boolean {
|
||||||
const d1 = disciplineId.toLowerCase().trim();
|
const d1 = disciplineId.toLowerCase().trim();
|
||||||
const d2 = targetId.toLowerCase().trim();
|
const d2 = targetId.toLowerCase().trim();
|
||||||
if (d1 === d2) return true;
|
if (d1 === d2) return true;
|
||||||
@@ -52,6 +52,8 @@ function matchDiscipline(disciplineId: string, targetId: string): boolean {
|
|||||||
abyssprimal: ['abyssprimal', 'magia otchłani', 'primal', 'eldritch', 'eldritch magic', 'abyss - primal', 'magia otchłani - pierwotna magia', 'pierwotna magia'],
|
abyssprimal: ['abyssprimal', 'magia otchłani', 'primal', 'eldritch', 'eldritch magic', 'abyss - primal', 'magia otchłani - pierwotna magia', 'pierwotna magia'],
|
||||||
witch: ['witch', 'wiedźmia magia', 'witch magic', 'dzika wiedźmia magia', 'wildwitch'],
|
witch: ['witch', 'wiedźmia magia', 'witch magic', 'dzika wiedźmia magia', 'wildwitch'],
|
||||||
blood: ['blood', 'magia krwi', 'blood magic'],
|
blood: ['blood', 'magia krwi', 'blood magic'],
|
||||||
|
sacred: ['sacred', 'magia sakralna', 'sacred magic'],
|
||||||
|
sacredexorcism: ['sacredexorcism', 'magia sakralna - egzorcyzmy', 'egzorcyzmy', 'exorcism', 'exorcisms', 'sacred - exorcisms'],
|
||||||
};
|
};
|
||||||
|
|
||||||
let key1: string | null = null;
|
let key1: string | null = null;
|
||||||
@@ -63,6 +65,8 @@ function matchDiscipline(disciplineId: string, targetId: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key1 && key2 && key1 === key2) return true;
|
if (key1 && key2 && key1 === key2) return true;
|
||||||
|
// Egzorcyzmy are a sub-discipline of Magia Sakralna: an exorcist counts as Sacred, not the other way round.
|
||||||
|
if (key1 === 'sacredexorcism' && key2 === 'sacred') return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"id": "hbm-rpg-v3",
|
"id": "hbm-rpg-v3",
|
||||||
"title": "Homebrew Magic: RPG v3",
|
"title": "Homebrew Magic: RPG v3",
|
||||||
"description": "System Foundry VTT dla Homebrew Magic: Role Playing Game v3 - autorska gra fabularna z mechaniką puli kości d6 (TS - Trudność:Sukcesy).",
|
"description": "System Foundry VTT dla Homebrew Magic: Role Playing Game v3 - autorska gra fabularna z mechaniką puli kości d6 (TS - Trudność:Sukcesy).",
|
||||||
"version": "1.5.1",
|
"version": "1.5.2",
|
||||||
"manifest": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3.json",
|
"manifest": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3.json",
|
||||||
"download": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3-v1.5.0.zip",
|
"download": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3-v1.5.0.zip",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
|
|||||||
@@ -104,14 +104,3 @@ describe('blood, elixirs, trade', () => {
|
|||||||
expect([0, 1, 2, 3, 4, 5, 6, 7, 12].map(negotiationBand)).toEqual([0, 1, 1, 2, 2, 3, 3, 4, 4]);
|
expect([0, 1, 2, 3, 4, 5, 6, 7, 12].map(negotiationBand)).toEqual([0, 1, 1, 2, 2, 3, 3, 4, 4]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('applyDiceModifier', () => {
|
|
||||||
it('adds a signed modifier and never goes below 0', async () => {
|
|
||||||
const { applyDiceModifier } = await import('../../src/logic/rules');
|
|
||||||
expect(applyDiceModifier(5, 2)).toBe(7);
|
|
||||||
expect(applyDiceModifier(5, -2)).toBe(3);
|
|
||||||
expect(applyDiceModifier(2, -5)).toBe(0);
|
|
||||||
expect(applyDiceModifier(4)).toBe(4);
|
|
||||||
expect(applyDiceModifier(4, 1.9)).toBe(5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { matchDiscipline } from '../../src/logic/spell-validation';
|
||||||
|
|
||||||
|
describe('matchDiscipline: Magia Sakralna - Egzorcyzmy', () => {
|
||||||
|
it('an exorcist counts as Sacred Magic for casting', () => {
|
||||||
|
expect(matchDiscipline('sacredExorcism', 'sacred')).toBe(true);
|
||||||
|
expect(matchDiscipline('Magia Sakralna - Egzorcyzmy', 'sacred')).toBe(true);
|
||||||
|
expect(matchDiscipline('Egzorcyzmy', 'Magia Sakralna')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a plain Sacred mage is not an exorcist', () => {
|
||||||
|
expect(matchDiscipline('sacred', 'sacredExorcism')).toBe(false);
|
||||||
|
expect(matchDiscipline('Magia Sakralna', 'egzorcyzmy')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not leak into other disciplines', () => {
|
||||||
|
expect(matchDiscipline('sacredExorcism', 'witch')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user