Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

88 lines
5.1 KiB
TypeScript

import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { assertMCPIdle, setMCPActive } from "../src/mcpActivity";
const state = vi.hoisted(() => ({
handlers: new Map<string, (event: any) => any>(),
drafts: vi.fn(() => [] as unknown[]),
sync: vi.fn().mockResolvedValue(undefined),
acknowledge: vi.fn().mockResolvedValue(undefined),
report: vi.fn().mockResolvedValue(undefined),
snapshot: vi.fn().mockResolvedValue({ operationId: "" }),
unregister: vi.fn(),
script: { running: false, applying: false },
}));
vi.mock("@wailsio/runtime", () => ({ Events: { On: (name: string, handler: (event: any) => any) => { state.handlers.set(name, handler); return vi.fn(); } } }));
vi.mock("../bindings/pvfine/services", () => ({ MCPService: { Acknowledge: state.acknowledge, ReportWindow: state.report, WindowState: state.snapshot, UnregisterWindow: state.unregister, CancelActiveScript: vi.fn() } }));
vi.mock("../src/stores/editor", () => ({ useEditorStore: () => ({ archiveDiffDrafts: state.drafts, syncMCPWorkspace: state.sync, closeAllTabs: vi.fn() }) }));
vi.mock("../src/stores/archive", () => ({ useArchiveStore: () => ({ loading: false }) }));
vi.mock("../src/stores/script", () => ({ useScriptStore: () => state.script }));
vi.mock("../src/stores/import", () => ({ useImportStore: () => ({ running: false }) }));
vi.mock("../src/stores/export", () => ({ useExportStore: () => ({ preparing: false, running: false }) }));
vi.mock("../src/stores/batch", () => ({ useBatchStore: () => ({ loading: false, applying: false }) }));
vi.mock("../src/stores/version", () => ({ useVersionStore: () => ({ committing: false }) }));
import { installMCPBridge } from "../src/mcpBridge";
let root: { inert: boolean };
let unload: (() => void) | undefined;
beforeEach(() => {
vi.clearAllMocks(); state.handlers.clear(); state.drafts.mockReturnValue([]); state.script.running = false;
state.snapshot.mockResolvedValue({ operationId: "" });
root = { inert: false };
const node = () => ({ style: { cssText: "", display: "" }, setAttribute: vi.fn(), appendChild: vi.fn(), addEventListener: vi.fn(), contains: () => false, remove: vi.fn(), textContent: "" });
vi.stubGlobal("document", { getElementById: () => root, createElement: node, body: { appendChild: vi.fn() } });
vi.stubGlobal("window", { addEventListener: (name: string, fn: () => void) => { if (name === "beforeunload") unload = fn; }, removeEventListener: vi.fn(), setInterval: vi.fn(() => 1), clearInterval: vi.fn() });
});
afterEach(() => { unload?.(); unload = undefined; setMCPActive(false); vi.unstubAllGlobals(); });
test("AI 准备屏障冻结编辑,拒绝未提交草稿,结束后释放", async () => {
installMCPBridge(false);
await vi.waitFor(() => expect(state.report).toHaveBeenCalled());
state.drafts.mockReturnValue([{}]);
await state.handlers.get("mcp:prepare")!({ data: { operationId: "operation-a", kind: "archive_save" } });
expect(root.inert).toBe(true);
expect(() => assertMCPIdle()).toThrow("AI 正在操作");
expect(state.acknowledge).toHaveBeenCalledWith("main", "operation-a", true, false);
await state.handlers.get("mcp:released")!({ data: { operationId: "operation-a" } });
expect(root.inert).toBe(false);
expect(() => assertMCPIdle()).not.toThrow();
});
test("GUI 草稿检查报错也按未提交草稿处理", async () => {
installMCPBridge(false);
state.drafts.mockImplementation(() => { throw new Error("GUI draft"); });
await state.handlers.get("mcp:prepare")!({ data: { operationId: "operation-b" } });
expect(state.acknowledge).toHaveBeenCalledWith("main", "operation-b", true, false);
});
test("窗口重载后恢复运行中的屏障,独立脚本源码不会计作归档草稿", async () => {
state.snapshot.mockResolvedValue({ operationId: "running-task", kind: "script_run" } as any);
installMCPBridge(true);
await vi.waitFor(() => expect(root.inert).toBe(true));
expect(state.drafts).not.toHaveBeenCalled();
expect(() => assertMCPIdle()).toThrow();
});
test("旧的心跳响应不能释放刚建立的准备屏障", async () => {
let resolve!: (value: any) => void;
state.snapshot.mockReturnValue(new Promise(done => { resolve = done; }));
installMCPBridge(false);
await vi.waitFor(() => expect(state.snapshot).toHaveBeenCalled());
await state.handlers.get("mcp:prepare")!({ data: { operationId: "new-operation" } });
resolve({ operationId: "" });
await Promise.resolve(); await Promise.resolve();
expect(root.inert).toBe(true);
expect(() => assertMCPIdle()).toThrow();
});
test("应用后的标签同步完成前,不释放界面也不确认下一个操作", async () => {
let resolve!: () => void;
state.sync.mockReturnValue(new Promise<void>(done => { resolve = done; }));
installMCPBridge(false);
await state.handlers.get("mcp:prepare")!({ data: { operationId: "apply-task" } });
state.handlers.get("mcp:workspace-changed")!({ data: { kind: "script_apply" } });
const releasing = state.handlers.get("mcp:released")!({ data: { operationId: "apply-task" } });
await Promise.resolve();
expect(root.inert).toBe(true);
resolve(); await releasing;
expect(state.sync).toHaveBeenCalled();
expect(root.inert).toBe(false);
});