fixed # 更新刘备配置和奖励

This commit is contained in:
2026-09-30 15:43:07 +08:00
parent 82d80397b6
commit 0ccb14cecd
22 changed files with 681 additions and 30 deletions
+81
View File
@@ -0,0 +1,81 @@
import { get } from "../../../../noname.js";
import { cardUpgradeRule } from "../cards/upgrades.js";
import { getRelic } from "../relics/definitions.js";
import {
chooseCardDialog, chooseRelicDialog, chooseVictoryOptions,
RETURN_TO_VICTORY,
} from "./reward-ui.js";
const upgradeDescription = choice => {
const card = choice.card;
const rule = cardUpgradeRule(card.name);
const damage = card.name === "sha" ? 6 : 8;
const suit = get.translation(card.suit);
return `${suit}${card.number} · 伤害 ${damage} → ${rule.damage}` +
(rule.vulnerable ? `;易伤 2 → ${rule.vulnerable} 层` : "") +
"。费用不变,最多强化一次。";
};
export async function chooseRewardPackage(choices, {
rewardPackage: pack, fixedRewards = [],
}) {
let cardId = null, relicId = null;
const upgradeChoices = pack.upgradeChoices.map(card => ({
id: card.id, card,
}));
const ready = () => (!upgradeChoices.length || cardId !== null) &&
(!pack.relicChoices.length || relicId !== null);
const upgrade = {
id: "upgrade", label: "强化一张牌",
description: upgradeChoices.length
? "从个人牌库中择一磨砺,查看强化前后的变化。"
: "当前没有可强化卡牌,此项无需选择。",
isDisabled: () => !upgradeChoices.length,
async choose() {
const selected = await chooseCardDialog(upgradeChoices, {
title: "磨砺 · 强化一张牌", skipLabel: "返回奖励",
description: "择一磨砺,锋芒更盛。只强化你选中的一张牌。",
describeChoice: upgradeDescription, actionLabel: "强化此牌",
});
if (selected !== null) {
cardId = selected;
const choice = upgradeChoices.find(card => card.id === selected);
upgrade.label = `已选强化 · ${get.translation(choice.card.name)}`;
upgrade.description = upgradeDescription(choice);
}
return RETURN_TO_VICTORY;
},
};
const relic = {
id: "relic", label: "遗物 · 三选一",
description: pack.relicChoices.length
? "检视本次随机珍藏,择一伴你踏上下一段征途。"
: "已持有奖励池中的全部遗物,此项无需选择。",
isDisabled: () => !pack.relicChoices.length,
async choose() {
const selected = await chooseRelicDialog(pack.relicChoices, {
skipLabel: "返回奖励", actionLabel: "携此遗物",
});
if (selected !== null) {
relicId = selected;
const choice = pack.relicChoices.find(item => item.id === selected);
const definition = getRelic(choice.relic);
relic.label = `已选遗物 · ${definition.name}`;
relic.description = definition.description;
}
return RETURN_TO_VICTORY;
},
};
return chooseVictoryOptions([upgrade, relic, {
id: "claim-package", label: "领取全部奖励",
description: "完成以上选择后,金币、强化、遗物与固定卡牌一并入囊。",
isDisabled: () => !ready(),
choose: () => ({ cardId, relicId }),
}], {
title: "初识的卢", allowSkip: false,
rewardLabel: "固定收获 · 完成两项选择后领取",
description: `固定获得 ${pack.gold} 金币与${fixedRewards.map(
reward => `【${reward.name}】`).join("、")}。` +
"另可强化一张牌,并从随机遗物中择一件。",
});
}
+55 -11
View File
@@ -1,6 +1,7 @@
import { lib, get } from "../../../../noname.js";
import { cardCost } from "../battle/combat-rules.js";
import { cardUpgradeRule } from "../cards/upgrades.js";
import { getRelic } from "../relics/definitions.js";
import { chooseButtons } from "./flow-ui.js";
const element = (tag, className, text, parent) => {
@@ -42,14 +43,39 @@ const viewCard = choice => {
};
const restoreFocus = previous => { if (previous?.isConnected) previous.focus(); };
const RETURN_TO_VICTORY = Symbol("return to victory choice");
export const RETURN_TO_VICTORY = Symbol("return to victory choice");
// Reusable card-only selector. It accepts three cards today and can lay out four or five later.
// Callers supply stable choice IDs; this UI never creates or awards cards itself.
export function chooseCardDialog(choices, { title = "选择一张牌", allowSkip = true, skipLabel = "跳过" } = {}) {
export function chooseCardDialog(choices, options = {}) {
if (!Array.isArray(choices) || !choices.length || choices.some(choice => !choice?.id || !isCardReward(choice))) {
return Promise.reject(new TypeError("chooseCardDialog requires nonempty card choices with IDs"));
}
return chooseTilesDialog(choices, options, viewCard);
}
export function chooseRelicDialog(choices, options = {}) {
if (!Array.isArray(choices) || !choices.length || choices.some(choice =>
!choice?.id || !getRelic(choice.relic))) {
return Promise.reject(new TypeError("遗物候选无效"));
}
return chooseTilesDialog(choices, {
title: "择一件遗物",
description: `${choices.length} 件珍藏,择一伴你远行。`,
actionLabel: "携此遗物",
...options,
}, choice => {
const relic = getRelic(choice.relic);
return { name: relic.name, type: "遗物", image: null, cost: null,
description: relic.description };
});
}
function chooseTilesDialog(choices, {
title = "选择一张牌", allowSkip = true, skipLabel = "跳过",
description = "择一入阵,余者留于身后", describeChoice,
actionLabel = "选择此牌",
} = {}, viewChoice) {
return new Promise((resolve, reject) => {
const previousFocus = document.activeElement;
const dialog = element("dialog", "mengsan-card-choice-dialog-shuying", null);
@@ -58,7 +84,7 @@ export function chooseCardDialog(choices, { title = "选择一张牌", allowSkip
const banner = element("header", "mengsan-card-choice-heading-shuying", null, stage);
element("span", "mengsan-reward-overline-shuying", "梦三 · 战利品", banner);
element("h2", "", title, banner);
element("p", "", "择一入阵,余者留于身后", banner);
element("p", "", description, banner);
const cards = element("div", "mengsan-card-choice-grid-shuying", null, stage);
cards.style.setProperty("--choice-count", String(Math.min(choices.length, 5)));
let settled = false;
@@ -71,12 +97,15 @@ export function chooseCardDialog(choices, { title = "选择一张牌", allowSkip
resolve(id);
};
for (const choice of choices) {
const view = viewCard(choice);
const view = viewChoice(choice);
if (describeChoice) view.description = describeChoice(choice);
const card = element("button", "mengsan-card-choice-tile-shuying", null, cards);
card.type = "button";
card.setAttribute("aria-label", `${view.name},${view.type},${view.description}。选择此牌`);
card.setAttribute("aria-label", `${view.name},${view.type},${view.description}。${actionLabel}`);
card.addEventListener("click", () => finish(choice.id));
element("span", "mengsan-card-choice-cost-shuying", String(view.cost), card);
if (view.cost !== null) {
element("span", "mengsan-card-choice-cost-shuying", String(view.cost), card);
}
element("span", "mengsan-card-choice-name-shuying", view.name, card);
const artwork = element("span", "mengsan-card-choice-art-shuying", null, card);
if (view.image) {
@@ -87,6 +116,7 @@ export function chooseCardDialog(choices, { title = "选择一张牌", allowSkip
element("span", "mengsan-card-choice-glyph-shuying", view.name.slice(0, 1), artwork);
element("span", "mengsan-card-choice-kind-shuying", view.type, card);
element("span", "mengsan-card-choice-description-shuying", view.description, card);
element("span", "mengsan-reward-tile-action-shuying", actionLabel, card);
}
if (allowSkip) {
const skip = element("button", "mengsan-card-choice-skip-shuying", skipLabel, stage);
@@ -125,6 +155,15 @@ export function chooseVictoryOptions(options, {
const board = element("div", "mengsan-victory-options-shuying", null, stage);
element("span", "mengsan-victory-eyebrow-shuying", rewardLabel, board);
let settled = false, choosing = false;
const entries = [];
const refresh = () => {
for (const { option, entry, label, detail } of entries) {
label.textContent = option.label;
detail.textContent = option.description || "";
entry.disabled = Boolean(option.isDisabled?.());
entry.setAttribute("aria-label", `${option.label}。${option.description || ""}`);
}
};
const finish = id => {
if (settled) return;
settled = true;
@@ -139,19 +178,20 @@ export function chooseVictoryOptions(options, {
entry.setAttribute("aria-label", `${option.label}。${option.description || ""}`);
element("span", "mengsan-victory-option-index-shuying", String(index + 1).padStart(2, "0"), entry);
const copy = element("span", "mengsan-victory-option-copy-shuying", null, entry);
element("strong", "", option.label, copy);
if (option.description) element("small", "", option.description, copy);
const label = element("strong", "", option.label, copy);
const detail = element("small", "", option.description || "", copy);
entries.push({ option, entry, label, detail });
element("span", "mengsan-victory-option-arrow-shuying", "›", entry).setAttribute("aria-hidden", "true");
entry.addEventListener("click", async () => {
if (settled || choosing) return;
if (settled || choosing || entry.disabled) return;
choosing = true;
entry.disabled = true;
if (dialog.open) dialog.close();
try {
const selected = option.choose ? await option.choose() : option.id;
if (selected === RETURN_TO_VICTORY) {
refresh();
dialog.showModal();
entry.disabled = false;
choosing = false;
entry.focus();
} else finish(selected);
@@ -161,6 +201,7 @@ export function chooseVictoryOptions(options, {
}
});
}
refresh();
if (allowSkip) {
const skip = element("button", "mengsan-victory-skip-shuying", "放弃本次选择", stage);
skip.type = "button";
@@ -169,7 +210,10 @@ export function chooseVictoryOptions(options, {
dialog.addEventListener("cancel", event => event.preventDefault());
dialog.addEventListener("keydown", event => event.stopPropagation());
document.documentElement.appendChild(dialog);
try { dialog.showModal(); board.querySelector("button")?.focus(); }
try {
dialog.showModal();
entries.find(({ entry }) => !entry.disabled)?.entry.focus();
}
catch (error) { dialog.remove(); restoreFocus(previousFocus); reject(error); }
});
}
+5
View File
@@ -1100,6 +1100,11 @@ button.mengsan-card-choice-tile-shuying:hover, button.mengsan-card-choice-tile-s
.mengsan-card-choice-glyph-shuying { color: #e9d4ae; font: 700 52px/1 "KaiTi",serif; }
.mengsan-card-choice-kind-shuying { align-self: center; margin-bottom: 6px; padding: 1px 12px; border: 1px solid #b99a6b; color: #f0dfba; font-size: 12px; }
.mengsan-card-choice-description-shuying { display: block !important; flex: 1; overflow: hidden; padding: 2px 3px 0; color: #e8e5d6; font-size: 13px; line-height: 1.55; }
.mengsan-reward-tile-action-shuying {
display: block !important; margin-top: 12px; padding: 10px 8px;
border-top: 1px solid #9e8261; color: #f4d7a0;
font: 700 16px/1.4 "KaiTi", "STKaiti", serif;
}
button.mengsan-card-choice-skip-shuying {
min-height: 48px; padding: 9px 28px; border: 1px solid #82978b; border-radius: 3px;
color: var(--reward-muted); background: #1a2b2f; font: 16px/1.4 "Microsoft YaHei",sans-serif; cursor: pointer;