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.
This commit is contained in:
parent
0fc61e2f94
commit
1d0f2f7c3e
|
|
@ -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<RollPayload>({
|
|||
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<RollPayload>({
|
|||
<span class="font-bold text-base">{result.total}</span>
|
||||
</span>
|
||||
</div>
|
||||
{result.detail && <p class="text-xs text-gray-400">{result.detail}</p>}
|
||||
{result.detail && (
|
||||
<p class="text-xs text-gray-400" innerHTML={result.detail} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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(`<strong>${roll.isNegative ? '-' : ''}${roll.value}</strong>`);
|
||||
const sign = roll.isNegative ? "-" : "";
|
||||
htmlSpans.push(`<strong>${sign}${roll.value}</strong>`);
|
||||
textSpans.push(`${sign}${roll.value}`);
|
||||
} else if (isKept) {
|
||||
rollSpans.push(`<strong>[${roll.value}]</strong>`);
|
||||
htmlSpans.push(`<strong>[${roll.value}]</strong>`);
|
||||
textSpans.push(`[${roll.value}]`);
|
||||
} else {
|
||||
rollSpans.push(`<span class="text-gray-400">[${roll.value}]</span>`);
|
||||
htmlSpans.push(`<span class="text-gray-400">[${roll.value}]</span>`);
|
||||
textSpans.push(`[${roll.value}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rollsStr = rollSpans.join(" + ");
|
||||
const detail = `<strong>${total}</strong> = ${rollsStr}`;
|
||||
const rollHtml = htmlSpans.join(" + ");
|
||||
const rollText = textSpans.join(" + ");
|
||||
const detail = `<strong>${total}</strong> = ${rollHtml}`;
|
||||
const plainDetail = `${total} = ${rollText}`;
|
||||
|
||||
return {
|
||||
pools: poolResults,
|
||||
total,
|
||||
detail,
|
||||
plainDetail,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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] = <strong>6</strong>
|
||||
*/
|
||||
export function rollSimple(formula: string): { text: string; isHtml?: boolean } {
|
||||
export function rollSimple(formula: string): {
|
||||
text: string;
|
||||
isHtml?: boolean;
|
||||
} {
|
||||
try {
|
||||
const result = rollDice(formula);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue