mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-30 19:50:04 +08:00
fixed # 偷偷更新GM菜单内容
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ import { configureDiagnostics } from "./diagnostics/index.js";
|
|||||||
|
|
||||||
export const type = "extension";
|
export const type = "extension";
|
||||||
export let shuYing = new Object();
|
export let shuYing = new Object();
|
||||||
const shuYingLocalVersion = "2.0.2.3-rc.5";
|
const shuYingLocalVersion = "2.0.2.3-rc.6";
|
||||||
|
|
||||||
shuYing.name = "术樱包";
|
shuYing.name = "术樱包";
|
||||||
shuYing.editable = false;
|
shuYing.editable = false;
|
||||||
|
|||||||
@@ -842,6 +842,8 @@ const requestBattleFinish = outcome => {
|
|||||||
recordBondDeath(player, _status.mengsanRun_shuying,
|
recordBondDeath(player, _status.mengsanRun_shuying,
|
||||||
message => game.log(message));
|
message => game.log(message));
|
||||||
}
|
}
|
||||||
|
// Capture the final HUD state while its battle session is still active.
|
||||||
|
current.hud?.refresh();
|
||||||
const accepted = current.flow.requestFinish({session:current.session, hp:game.me?.hp || 0, outcome, defeatedEnemies:current.director?.defeatedEnemies ?? 1});
|
const accepted = current.flow.requestFinish({session:current.session, hp:game.me?.hp || 0, outcome, defeatedEnemies:current.director?.defeatedEnemies ?? 1});
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
current.pilesUI?.dispose();
|
current.pilesUI?.dispose();
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export function openCardLibrary(run, options = {}) {
|
|||||||
const existing = document.querySelector("dialog.mengsan-deck-dialog-shuying[open]");
|
const existing = document.querySelector("dialog.mengsan-deck-dialog-shuying[open]");
|
||||||
if (existing) { existing.focus(); return () => {}; }
|
if (existing) { existing.focus(); return () => {}; }
|
||||||
const previousFocus = document.activeElement;
|
const previousFocus = document.activeElement;
|
||||||
|
const selection = options.selection;
|
||||||
const dialog = element("dialog", "mengsan-deck-dialog-shuying", null);
|
const dialog = element("dialog", "mengsan-deck-dialog-shuying", null);
|
||||||
dialog.setAttribute("aria-label", options.title || "卡牌库");
|
dialog.setAttribute("aria-label", options.title || "卡牌库");
|
||||||
const header = element("header", "ms-deck-header", null, dialog);
|
const header = element("header", "ms-deck-header", null, dialog);
|
||||||
@@ -143,12 +144,13 @@ export function openCardLibrary(run, options = {}) {
|
|||||||
onClose?.(close);
|
onClose?.(close);
|
||||||
};
|
};
|
||||||
button("关闭", header, close);
|
button("关闭", header, close);
|
||||||
let view = !options.sections && options.view === "catalog" ? "catalog" : "deck";
|
let view = selection || (!options.sections && options.view === "catalog")
|
||||||
|
? "catalog" : "deck";
|
||||||
const navigation = element("nav", "ms-deck-tabs", null, dialog);
|
const navigation = element("nav", "ms-deck-tabs", null, dialog);
|
||||||
navigation.setAttribute("aria-label", "卡牌列表视图");
|
navigation.setAttribute("aria-label", "卡牌列表视图");
|
||||||
const deckTab = button("持有牌", navigation, () => { view = "deck"; render(); });
|
const deckTab = button("持有牌", navigation, () => { view = "deck"; render(); });
|
||||||
const catalogTab = button("图鉴", navigation, () => { view = "catalog"; render(); });
|
const catalogTab = button("图鉴", navigation, () => { view = "catalog"; render(); });
|
||||||
deckTab.hidden = catalogTab.hidden = Boolean(options.sections);
|
deckTab.hidden = catalogTab.hidden = Boolean(options.sections || selection);
|
||||||
let sectionIndex = 0;
|
let sectionIndex = 0;
|
||||||
const sectionTabs = options.sections ? options.sections().map((section, index) =>
|
const sectionTabs = options.sections ? options.sections().map((section, index) =>
|
||||||
button(section.title, navigation, () => { sectionIndex = index; render(); })) : [];
|
button(section.title, navigation, () => { sectionIndex = index; render(); })) : [];
|
||||||
@@ -222,6 +224,24 @@ export function openCardLibrary(run, options = {}) {
|
|||||||
element("p", "ms-deck-rules", details.description, copy);
|
element("p", "ms-deck-rules", details.description, copy);
|
||||||
if (!details.available) element("p", "ms-deck-warning", "该卡牌定义未加载;保留存档记录,不删除此牌。", copy);
|
if (!details.available) element("p", "ms-deck-warning", "该卡牌定义未加载;保留存档记录,不删除此牌。", copy);
|
||||||
for (const affix of details.affixes) element("p", "ms-deck-rules", affix, copy);
|
for (const affix of details.affixes) element("p", "ms-deck-rules", affix, copy);
|
||||||
|
if (catalog && selection) {
|
||||||
|
const reason = !details.available ? "该卡牌定义未加载。" :
|
||||||
|
selection.unavailableReason?.(card) || "";
|
||||||
|
const feedback = element("p", "ms-deck-rules", reason, copy);
|
||||||
|
feedback.setAttribute("role", "status");
|
||||||
|
const confirm = button(selection.label, copy, () => {
|
||||||
|
if (closed || confirm.disabled) return;
|
||||||
|
confirm.disabled = true;
|
||||||
|
try {
|
||||||
|
selection.onSelect(card);
|
||||||
|
close();
|
||||||
|
} catch (error) {
|
||||||
|
feedback.textContent = error.message || "添加失败,请重试。";
|
||||||
|
confirm.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
confirm.disabled = Boolean(reason);
|
||||||
|
}
|
||||||
body.scrollTop = 0; backButton.focus();
|
body.scrollTop = 0; backButton.focus();
|
||||||
}
|
}
|
||||||
function fitCards() {
|
function fitCards() {
|
||||||
@@ -296,7 +316,8 @@ export function openCardLibrary(run, options = {}) {
|
|||||||
summary.textContent = options.sections ? "剩余牌堆按从左到右、从上到下的顺序摸取;第 1 张为下一张。" :
|
summary.textContent = options.sections ? "剩余牌堆按从左到右、从上到下的顺序摸取;第 1 张为下一张。" :
|
||||||
`本次征程实际携带 · 共 ${current.length} 张 · 同名不同花色、点数分别展示`;
|
`本次征程实际携带 · 共 ${current.length} 张 · 同名不同花色、点数分别展示`;
|
||||||
}
|
}
|
||||||
footer.textContent = catalog ? "图鉴仅供查阅,不会获得卡牌或改变征程牌组 · 点击卡牌查看完整介绍" :
|
if (catalog && selection) footer.textContent = selection.description;
|
||||||
|
else footer.textContent = catalog ? "图鉴仅供查阅,不会获得卡牌或改变征程牌组 · 点击卡牌查看完整介绍" :
|
||||||
"点击卡牌查看完整介绍 · 仅供查看,不改变牌组或抽牌顺序";
|
"点击卡牌查看完整介绍 · 仅供查看,不改变牌组或抽牌顺序";
|
||||||
virtualGrid.setRecords(catalog || options.sections ? records : current.map(card => ({ card })));
|
virtualGrid.setRecords(catalog || options.sections ? records : current.map(card => ({ card })));
|
||||||
if (!(catalog || options.sections ? records : current).length) {
|
if (!(catalog || options.sections ? records : current).length) {
|
||||||
|
|||||||
+101
-4
@@ -1,6 +1,20 @@
|
|||||||
import { AFFIX_INFO } from "../cards/affixes.js";
|
import { AFFIX_INFO } from "../cards/affixes.js";
|
||||||
import { hasCardAffix } from "../cards/intrinsic-affixes.js";
|
import { hasCardAffix } from "../cards/intrinsic-affixes.js";
|
||||||
import { describeLibraryCard } from "./card-library.js";
|
import { describeLibraryCard, openCardLibrary } from "./card-library.js";
|
||||||
|
import { cardDefinitions } from "../cards/card-definitions.js";
|
||||||
|
import { createCardData } from "../cards/card-data.js";
|
||||||
|
import { DAZED_NAME, createDazedData } from "../cards/status-cards.js";
|
||||||
|
import { SLIMED_NAME, createSlimedData } from "../cards/slimed-card.js";
|
||||||
|
import { WOUND_NAME, createWoundData } from "../cards/wound-card.js";
|
||||||
|
import { INFECTION_NAME, createInfectionData } from "../cards/infection-card.js";
|
||||||
|
import { getDiagnostics } from "../../diagnostics/index.js";
|
||||||
|
|
||||||
|
const statusFactories = {
|
||||||
|
[DAZED_NAME]: createDazedData,
|
||||||
|
[SLIMED_NAME]: createSlimedData,
|
||||||
|
[WOUND_NAME]: createWoundData,
|
||||||
|
[INFECTION_NAME]: createInfectionData,
|
||||||
|
};
|
||||||
|
|
||||||
const node = (tag, className, label, parent) => {
|
const node = (tag, className, label, parent) => {
|
||||||
const element = document.createElement(tag);
|
const element = document.createElement(tag);
|
||||||
@@ -20,6 +34,10 @@ const button = (label, parent, action) => {
|
|||||||
// Keep the command battle-scoped; further GM actions can be added as panels here.
|
// Keep the command battle-scoped; further GM actions can be added as panels here.
|
||||||
export function mountGMManager(session, owner, piles, battle, resources, game) {
|
export function mountGMManager(session, owner, piles, battle, resources, game) {
|
||||||
let dialog = null, selected = null, previousFocus = null, disposed = false;
|
let dialog = null, selected = null, previousFocus = null, disposed = false;
|
||||||
|
let closePicker = null, sequence = 0, message = "";
|
||||||
|
const diagnostics = getDiagnostics().scope("mengsan.gm", () => ({
|
||||||
|
sessionId: session.id,
|
||||||
|
}));
|
||||||
const available = () => !disposed && session.active && !battle.resolving;
|
const available = () => !disposed && session.active && !battle.resolving;
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (!dialog) return;
|
if (!dialog) return;
|
||||||
@@ -32,6 +50,7 @@ export function mountGMManager(session, owner, piles, battle, resources, game) {
|
|||||||
};
|
};
|
||||||
const render = () => {
|
const render = () => {
|
||||||
if (!dialog) return;
|
if (!dialog) return;
|
||||||
|
dialog.querySelector(".ms-gm-feedback").textContent = message;
|
||||||
const list = dialog.querySelector(".ms-gm-hand");
|
const list = dialog.querySelector(".ms-gm-hand");
|
||||||
const editor = dialog.querySelector(".ms-gm-editor");
|
const editor = dialog.querySelector(".ms-gm-editor");
|
||||||
list.replaceChildren(); editor.replaceChildren();
|
list.replaceChildren(); editor.replaceChildren();
|
||||||
@@ -60,9 +79,79 @@ export function mountGMManager(session, owner, piles, battle, resources, game) {
|
|||||||
item.title = info.description;
|
item.title = info.description;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const chooseTemporaryCard = () => {
|
||||||
|
if (!available() || closePicker) return;
|
||||||
|
close();
|
||||||
|
closePicker = openCardLibrary(null, {
|
||||||
|
title: "添加临时牌", eyebrow: "梦三 · GM",
|
||||||
|
view: "catalog",
|
||||||
|
selection: {
|
||||||
|
label: "加入手牌 · 仅本场战斗",
|
||||||
|
description: "选择卡牌查看详情并加入手牌;正常弃置和洗牌," +
|
||||||
|
"战斗结束清理。默认黑桃 A、未强化。",
|
||||||
|
unavailableReason: card =>
|
||||||
|
Object.hasOwn(cardDefinitions, card.name) ||
|
||||||
|
Object.hasOwn(statusFactories, card.name) ? "" :
|
||||||
|
"该卡牌尚无可用的战斗创建规则。",
|
||||||
|
onSelect(card) {
|
||||||
|
if (!available() || !owner.isAlive()) {
|
||||||
|
throw new Error("当前战斗已结束或角色已阵亡,无法添加。");
|
||||||
|
}
|
||||||
|
if (!describeLibraryCard(card).available) {
|
||||||
|
throw new Error("该卡牌定义未加载。");
|
||||||
|
}
|
||||||
|
const id = `mengsan_gm_${session.id}_${++sequence}`;
|
||||||
|
const factory = Object.hasOwn(statusFactories, card.name)
|
||||||
|
? statusFactories[card.name] : null;
|
||||||
|
const data = factory ? factory(id) : createCardData({
|
||||||
|
id, name: card.name, suit: "spade", number: 1,
|
||||||
|
nature: null, affixes: [], upgrade: 0,
|
||||||
|
}, { enemy: true });
|
||||||
|
// GM injection must not await the paused native event queue.
|
||||||
|
const created = piles.addToDiscard(data);
|
||||||
|
if (!created) throw new Error("当前战斗无法创建临时牌。");
|
||||||
|
try {
|
||||||
|
owner.directgain([created]);
|
||||||
|
if (!owner.getCards("h").includes(created)) {
|
||||||
|
throw new Error("临时牌未能加入手牌。");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
created.remove();
|
||||||
|
diagnostics.error("temporaryCard.failed", error, {
|
||||||
|
cardName: data.name, cardId: id,
|
||||||
|
});
|
||||||
|
game.log("GM 临时牌添加失败", data.name, id,
|
||||||
|
error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
selected = created;
|
||||||
|
message = `已将【${describeLibraryCard(data).name}】加入手牌,` +
|
||||||
|
"仅本场战斗有效。";
|
||||||
|
game.log("GM 添加临时牌", owner, data.name, id,
|
||||||
|
session.id);
|
||||||
|
diagnostics.info("temporaryCard.added", {
|
||||||
|
cardName: data.name, cardId: id,
|
||||||
|
});
|
||||||
|
try { game.check(); }
|
||||||
|
catch (error) {
|
||||||
|
game.log("GM 临时牌已入手,选择状态刷新失败",
|
||||||
|
error.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
closePicker = null;
|
||||||
|
if (available()) open();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
const open = () => {
|
const open = () => {
|
||||||
if (!available()) return false;
|
if (!available()) return false;
|
||||||
|
if (closePicker) return true;
|
||||||
if (dialog?.open) { render(); dialog.focus(); return true; }
|
if (dialog?.open) { render(); dialog.focus(); return true; }
|
||||||
|
const existing = document.querySelector(
|
||||||
|
"dialog.mengsan-deck-dialog-shuying[open]");
|
||||||
|
if (existing) { existing.focus(); return false; }
|
||||||
previousFocus = document.activeElement;
|
previousFocus = document.activeElement;
|
||||||
dialog = node("dialog", "mengsan-deck-dialog-shuying mengsan-gm-dialog-shuying", null);
|
dialog = node("dialog", "mengsan-deck-dialog-shuying mengsan-gm-dialog-shuying", null);
|
||||||
dialog.setAttribute("aria-label", "梦三 GM 管理器");
|
dialog.setAttribute("aria-label", "梦三 GM 管理器");
|
||||||
@@ -70,8 +159,12 @@ export function mountGMManager(session, owner, piles, battle, resources, game) {
|
|||||||
const header = node("header", "ms-deck-header", null, dialog);
|
const header = node("header", "ms-deck-header", null, dialog);
|
||||||
node("h2", "ms-deck-title", "GM 管理器", header);
|
node("h2", "ms-deck-title", "GM 管理器", header);
|
||||||
button("关闭", header, close);
|
button("关闭", header, close);
|
||||||
node("p", "ms-deck-summary", "手牌词缀 · 仅本场战斗生效,不改永久牌组或存档。", dialog);
|
node("p", "ms-deck-summary",
|
||||||
|
"手牌词缀 / 添加临时牌 · 仅本场战斗生效,不改永久牌组或存档。", dialog);
|
||||||
|
button("添加临时牌", dialog, chooseTemporaryCard);
|
||||||
button("刷新手牌", dialog, render);
|
button("刷新手牌", dialog, render);
|
||||||
|
const feedback = node("p", "ms-gm-feedback", "", dialog);
|
||||||
|
feedback.setAttribute("role", "status");
|
||||||
node("div", "ms-gm-hand", null, dialog);
|
node("div", "ms-gm-hand", null, dialog);
|
||||||
node("section", "ms-gm-editor", null, dialog);
|
node("section", "ms-gm-editor", null, dialog);
|
||||||
const created = dialog;
|
const created = dialog;
|
||||||
@@ -85,7 +178,11 @@ export function mountGMManager(session, owner, piles, battle, resources, game) {
|
|||||||
};
|
};
|
||||||
resources.field(globalThis, "mengsanGM_shuying", open);
|
resources.field(globalThis, "mengsanGM_shuying", open);
|
||||||
resources.field(game, "mengsanGM_shuying", open);
|
resources.field(game, "mengsanGM_shuying", open);
|
||||||
const handle = { dispose: close };
|
const handle = { dispose() {
|
||||||
session.ownResource(handle, () => { disposed = true; close(); });
|
disposed = true;
|
||||||
|
closePicker?.(); closePicker = null;
|
||||||
|
close();
|
||||||
|
} };
|
||||||
|
session.ownResource(handle, handle.dispose);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ const effects = {
|
|||||||
lossStrength: ['撕裂', 'rupture', '力量', '自身回合失去生命时获得力量。'],
|
lossStrength: ['撕裂', 'rupture', '力量', '自身回合失去生命时获得力量。'],
|
||||||
vulnerableBonus: ['易伤增幅', 'vicious', '倍率加成', '对易伤敌人的伤害倍率额外加成。'],
|
vulnerableBonus: ['易伤增幅', 'vicious', '倍率加成', '对易伤敌人的伤害倍率额外加成。'],
|
||||||
attackBlock: ['狂怒', 'rage', '格挡', '本回合每打出一张攻击牌获得格挡。'],
|
attackBlock: ['狂怒', 'rage', '格挡', '本回合每打出一张攻击牌获得格挡。'],
|
||||||
retaliation: ['火焰屏障', 'flame_barrier', '伤害', '本回合受到攻击后反击。'],
|
retaliation: ['火焰屏障', 'flame_barrier', '伤害', '至下个自身回合开始,受到攻击后反击。'],
|
||||||
colossus: ['巨像', 'colossus', '', '本回合受到易伤敌人的攻击伤害减少50%。'],
|
colossus: ['巨像', 'colossus', '', '至下个自身回合开始,受到易伤敌人的攻击伤害减少50%。'],
|
||||||
freeAttack: ['免费攻击', 'free_attack', '', '下一张符合规则的攻击牌免费。'],
|
freeAttack: ['免费攻击', 'free_attack', '', '下一张符合规则的攻击牌免费。'],
|
||||||
noDraw: ['不可抽牌', 'no_draw', '', '本回合不能继续抽牌。'],
|
noDraw: ['不可抽牌', 'no_draw', '', '本回合不能继续抽牌。'],
|
||||||
noEnergy: ['无法获得费用', 'no_energy_gain', '', '本回合不能再获得费用。'],
|
noEnergy: ['无法获得费用', 'no_energy_gain', '', '本回合不能再获得费用。'],
|
||||||
|
|||||||
Reference in New Issue
Block a user