feat: dice modifier in the spell cast dialog

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E48f1kXhsEbAUxdkNP1yDM
This commit is contained in:
2026-10-26 21:31:20 +01:00
co-authored by Claude Sonnet 5.5
parent 582b189ce6
commit 27c900e26d
7 changed files with 43 additions and 5 deletions
+5
View File
@@ -259,3 +259,8 @@ export function negotiationBand(successes: number): number {
export function fearThresholdMod(sourceId: string | undefined, targetId: string | undefined): number {
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));
}
+10 -5
View File
@@ -16,7 +16,7 @@
import { HbmTSRoll } from '../dice/ts-roll';
import { TS_DEFAULT_REQUIRED, TS_DEFAULT_THRESHOLD, getMagicPowerEntry } from '../constants';
import { manaToBorrow, exhaustionDice, manaExhaustionTier, overLimitTest, SPELL_DAMAGE_PROFILES } from './rules';
import { manaToBorrow, exhaustionDice, manaExhaustionTier, overLimitTest, applyDiceModifier, SPELL_DAMAGE_PROFILES } from './rules';
import { validateCast } from './spell-validation';
import type { ValidationResult } from './spell-validation';
import { rollSpellDamage } from './spell-damage';
@@ -76,6 +76,8 @@ export interface CastOptions {
bypassSuperspellWarning?: boolean;
bypassNonCombatBlock?: boolean;
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). */
requiredOverride?: number;
speaker?: ChatMessage.SpeakerData;
@@ -276,9 +278,10 @@ async function castStandard(actor: CastableActor, spell: SpellLike, opts: CastOp
const magicDice = typeof a.magic.dicePool === 'number'
? a.magic.dicePool
: getMagicPowerEntry(a.magic.actual ?? a.magic.value ?? 0).dicePool;
const pool = opts.castAsPrayer
const basePool = opts.castAsPrayer
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
const pool = applyDiceModifier(basePool, opts.diceModifier);
const flavor = opts.castAsPrayer
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')}`
: `${spell.name}`;
@@ -349,7 +352,7 @@ async function castSacred(actor: CastableActor, spell: SpellLike, opts: CastOpti
const zealCost = Math.max(1, opts.zealSpent ?? 1);
await actor.update({ 'system.attributes.zeal.value': a.zeal.value - zealCost });
const pool = a.soul.value + (actor.system.skills?.devotion?.value ?? 0);
const pool = applyDiceModifier(a.soul.value + (actor.system.skills?.devotion?.value ?? 0), opts.diceModifier);
const flavor = `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.sacred')}`;
const required = opts.requiredOverride ?? spell.system.difficulty.successes ?? TS_DEFAULT_REQUIRED;
const roll = HbmTSRoll.fromParams({
@@ -379,9 +382,10 @@ async function castWitch(actor: CastableActor, spell: SpellLike, opts: CastOptio
const magicDice = typeof (a.magic as any).dicePool === 'number'
? (a.magic as any).dicePool
: getMagicPowerEntry((a.magic as any).actual ?? a.magic.value ?? 0).dicePool;
const pool = opts.castAsPrayer
const basePool = opts.castAsPrayer
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
const pool = applyDiceModifier(basePool, opts.diceModifier);
const flavor = opts.castAsPrayer
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')} (${required}/${maxSymbols})`
: `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.witch')} (${required}/${maxSymbols})`;
@@ -421,9 +425,10 @@ async function castBlood(actor: CastableActor, spell: SpellLike, opts: CastOptio
const magicDice = typeof (a.magic as any).dicePool === 'number'
? (a.magic as any).dicePool
: getMagicPowerEntry((a.magic as any).actual ?? a.magic.value ?? 0).dicePool;
const pool = opts.castAsPrayer
const basePool = opts.castAsPrayer
? a.soul.value + (actor.system.skills?.devotion?.value ?? 0)
: magicDice + (actor.system.skills?.magicalAbilities?.value ?? 0);
const pool = applyDiceModifier(basePool, opts.diceModifier);
const flavor = opts.castAsPrayer
? `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.prayer')}`
: `${spell.name} - ${game.i18n.localize('HBM.spell.castingMode.blood')}`;