From 1d0f2f7c3e5e44b005e3860354363396f2fcf5ba Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 6 Jul 2026 18:18:16 +0800 Subject: [PATCH] feat: add plain text dice roll detail Introduces `plainDetail` to the dice engine to provide a non-HTML version of the roll results. This allows components to display the roll breakdown without needing to parse or render HTML. --- src/components/journal/types/roll.tsx | 7 +++- .../md-commander/hooks/dice-engine/roller.ts | 37 +++++++++++------ .../md-commander/hooks/dice-engine/types.ts | 41 ++++++++++--------- .../md-commander/hooks/useDiceRoller.ts | 8 +++- src/components/md-dice.tsx | 10 ++--- 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/components/journal/types/roll.tsx b/src/components/journal/types/roll.tsx index c2b13a2..568d6b1 100644 --- a/src/components/journal/types/roll.tsx +++ b/src/components/journal/types/roll.tsx @@ -16,6 +16,7 @@ const schema = z.object({ result: z.object({ total: z.number(), detail: z.string(), + plainDetail: z.string(), pools: z.array( z.object({ rolls: z.array(z.number()), @@ -47,7 +48,7 @@ registerMessageType({ schema, defaultPayload: () => ({ notation: "1d20", - result: { total: 0, detail: "", pools: [] }, + result: { total: 0, detail: "", plainDetail: "", pools: [] }, }), render: (p) => { const { result } = p; @@ -60,7 +61,9 @@ registerMessageType({ {result.total} - {result.detail &&

{result.detail}

} + {result.detail && ( +

+ )} ); }, diff --git a/src/components/md-commander/hooks/dice-engine/roller.ts b/src/components/md-commander/hooks/dice-engine/roller.ts index d0b8e66..dc356a8 100644 --- a/src/components/md-commander/hooks/dice-engine/roller.ts +++ b/src/components/md-commander/hooks/dice-engine/roller.ts @@ -38,7 +38,10 @@ export function rollDicePool(pool: DicePool): DicePoolResult { const { keptRolls, droppedRolls } = applyModifier(rolls, pool.modifier); // 固定数字总是保留 - const finalKeptRolls = [...keptRolls, ...allRolls.filter((r) => r.sides === 0)]; + const finalKeptRolls = [ + ...keptRolls, + ...allRolls.filter((r) => r.sides === 0), + ]; // 计算小计 const subtotal = finalKeptRolls.reduce((sum, roll) => { @@ -59,7 +62,7 @@ export function rollDicePool(pool: DicePool): DicePoolResult { */ export function applyModifier( rolls: DieResult[], - modifier: DicePool["modifier"] + modifier: DicePool["modifier"], ): { keptRolls: DieResult[]; droppedRolls: DieResult[] } { if (!modifier) { return { keptRolls: rolls, droppedRolls: [] }; @@ -70,7 +73,8 @@ export function applyModifier( switch (modifier.type) { case "kh": // Keep Highest - 保留最大的 N 个 - case "dh": { // Drop Highest - 丢弃最大的 N 个 + case "dh": { + // Drop Highest - 丢弃最大的 N 个 const count = modifier.count; const higher = sorted.slice(-count); const rest = sorted.slice(0, -count); @@ -81,7 +85,8 @@ export function applyModifier( } } case "kl": // Keep Lowest - 保留最小的 N 个 - case "dl": { // Drop Lowest - 丢弃最小的 N 个 + case "dl": { + // Drop Lowest - 丢弃最小的 N 个 const count = modifier.count; const lower = sorted.slice(0, count); const rest = sorted.slice(count); @@ -105,29 +110,35 @@ export function roll(formula: string): RollResult { const total = poolResults.reduce((sum, result) => sum + result.subtotal, 0); - // 生成详细表达式 - 使用 HTML 格式 - const rollSpans: string[] = []; + // Generate detail expressions (both HTML and plain text) + const htmlSpans: string[] = []; + const textSpans: string[] = []; for (const result of poolResults) { - // 构建骰子结果 HTML for (const roll of result.rolls) { const isKept = result.keptRolls.some((kept) => kept === roll); if (roll.sides === 0) { - // 固定数字,不显示括号 - rollSpans.push(`${roll.isNegative ? '-' : ''}${roll.value}`); + const sign = roll.isNegative ? "-" : ""; + htmlSpans.push(`${sign}${roll.value}`); + textSpans.push(`${sign}${roll.value}`); } else if (isKept) { - rollSpans.push(`[${roll.value}]`); + htmlSpans.push(`[${roll.value}]`); + textSpans.push(`[${roll.value}]`); } else { - rollSpans.push(`[${roll.value}]`); + htmlSpans.push(`[${roll.value}]`); + textSpans.push(`[${roll.value}]`); } } } - const rollsStr = rollSpans.join(" + "); - const detail = `${total} = ${rollsStr}`; + const rollHtml = htmlSpans.join(" + "); + const rollText = textSpans.join(" + "); + const detail = `${total} = ${rollHtml}`; + const plainDetail = `${total} = ${rollText}`; return { pools: poolResults, total, detail, + plainDetail, }; } diff --git a/src/components/md-commander/hooks/dice-engine/types.ts b/src/components/md-commander/hooks/dice-engine/types.ts index a516681..476840c 100644 --- a/src/components/md-commander/hooks/dice-engine/types.ts +++ b/src/components/md-commander/hooks/dice-engine/types.ts @@ -1,6 +1,6 @@ /** * 骰子引擎类型定义 - * + * * 支持语法: * - 3d6: 标准骰子表示 * - {d4, d8}: 骰池字面量 @@ -14,8 +14,8 @@ * 单个骰子结果 */ export interface DieResult { - sides: number; // 骰子面数,0 表示固定数字 - value: number; // 掷骰结果 + sides: number; // 骰子面数,0 表示固定数字 + value: number; // 掷骰结果 isNegative: boolean; // 是否为负数 } @@ -23,9 +23,9 @@ export interface DieResult { * 骰池 */ export interface DicePool { - dice: DieResult[]; // 骰子列表(未掷骰前为模板) + dice: DieResult[]; // 骰子列表(未掷骰前为模板) modifier?: { - type: 'kh' | 'kl' | 'dh' | 'dl'; + type: "kh" | "kl" | "dh" | "dl"; count: number; }; isNegative: boolean; // 整个骰池是否为负 @@ -35,31 +35,32 @@ export interface DicePool { * 掷骰结果 */ export interface RollResult { - pools: DicePoolResult[]; // 各骰池结果 - total: number; // 总和 - detail: string; // 详细表达式 + pools: DicePoolResult[]; // 各骰池结果 + total: number; // 总和 + detail: string; // 详细表达式 (HTML) + plainDetail: string; // 详细表达式 (plain text) } /** * 单个骰池的掷骰结果 */ export interface DicePoolResult { - pool: DicePool; // 原始骰池定义 - rolls: DieResult[]; // 掷骰结果 - keptRolls: DieResult[]; // 保留的骰子(应用修饰符后) + pool: DicePool; // 原始骰池定义 + rolls: DieResult[]; // 掷骰结果 + keptRolls: DieResult[]; // 保留的骰子(应用修饰符后) droppedRolls: DieResult[]; // 丢弃的骰子 - subtotal: number; // 该骰池小计 + subtotal: number; // 该骰池小计 } /** * 解析后的令牌 */ export type Token = - | { type: 'number'; value: number } - | { type: 'dice'; count: number; sides: number } - | { type: 'pool_start' } - | { type: 'pool_end' } - | { type: 'comma' } - | { type: 'plus' } - | { type: 'minus' } - | { type: 'modifier'; modType: 'kh' | 'kl' | 'dh' | 'dl'; count: number }; + | { type: "number"; value: number } + | { type: "dice"; count: number; sides: number } + | { type: "pool_start" } + | { type: "pool_end" } + | { type: "comma" } + | { type: "plus" } + | { type: "minus" } + | { type: "modifier"; modType: "kh" | "kl" | "dh" | "dl"; count: number }; diff --git a/src/components/md-commander/hooks/useDiceRoller.ts b/src/components/md-commander/hooks/useDiceRoller.ts index c20aad0..9a258bc 100644 --- a/src/components/md-commander/hooks/useDiceRoller.ts +++ b/src/components/md-commander/hooks/useDiceRoller.ts @@ -5,6 +5,7 @@ export interface DiceRollerResult { result: { total: number; detail: string; + plainDetail: string; pools: Array<{ rolls: number[]; keptRolls: number[]; @@ -38,6 +39,7 @@ export function rollFormula(formula: string): DiceRollerResult { result: { total: result.total, detail: result.detail, + plainDetail: result.plainDetail, pools: result.pools.map((pool) => ({ rolls: pool.rolls.map((r) => r.value), keptRolls: pool.keptRolls.map((r) => r.value), @@ -52,6 +54,7 @@ export function rollFormula(formula: string): DiceRollerResult { result: { total: 0, detail: "", + plainDetail: "", pools: [], }, success: false, @@ -64,7 +67,10 @@ export function rollFormula(formula: string): DiceRollerResult { * 简化版掷骰,返回 HTML 格式结果 * 格式:[1] [2] [3] = 6 */ -export function rollSimple(formula: string): { text: string; isHtml?: boolean } { +export function rollSimple(formula: string): { + text: string; + isHtml?: boolean; +} { try { const result = rollDice(formula); return { diff --git a/src/components/md-dice.tsx b/src/components/md-dice.tsx index 294320d..8ee138e 100644 --- a/src/components/md-dice.tsx +++ b/src/components/md-dice.tsx @@ -1,6 +1,6 @@ import { customElement, noShadowDOM } from "solid-element"; import { createSignal, onMount } from "solid-js"; -import {rollFormula} from "./md-commander/hooks"; +import { rollFormula } from "./md-commander/hooks"; export interface DiceProps { key?: string; @@ -34,7 +34,7 @@ customElement("md-dice", { key: "" }, (props, { element }) => { // 从 element 的 textContent 获取骰子公式 const formula = element?.textContent?.trim() || ""; - + // 隐藏原始文本内容 if (element) { element.textContent = ""; @@ -57,9 +57,9 @@ customElement("md-dice", { key: "" }, (props, { element }) => { const handleRoll = (e: MouseEvent) => { e.preventDefault(); // 点击骰子图标:总是重 roll - const rollResult = rollFormula(formula).result; - setResult(rollResult.total); - setRollDetail(rollResult.detail); + const rollResult = rollFormula(formula); + setResult(rollResult.result.total); + setRollDetail(rollResult.result.plainDetail); setIsRolled(true); if (effectiveKey()) { setDiceResultToUrl(effectiveKey(), rollResult.total);