feat: simplify SCUM activity management
This commit is contained in:
@@ -4,7 +4,7 @@ import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { migrateConfigurationRecord, migrateGiftGrantRecord, migratePlayerProfileRecord, migratePlayerRecord, migrateStatePatchRecord, migrateTrajectoryHistoryRecord, migrateTrajectoryRecord, migrationStatus } from "../examples/scum-server-plugin/features/migration.js";
|
||||
import { createGiftDelivery, deleteGiftDefinition, loadSCUMSurface, parseGiftItems, queueGiftDelivery, resetGiftClaim, resetPendingGift, resolveMapBounds, saveEventProduce, saveGiftDefinition, saveMapSettings, scumCollections, selectGiftItems, startEvent, type RecordMap, type SCUMSurfaceData, type SCUMWorkspaceActions } from "../examples/scum-server-plugin/features/page-data.js";
|
||||
import { createGiftDelivery, deleteGiftDefinition, loadSCUMSurface, parseGiftItems, queueGiftDelivery, resetGiftClaim, resetPendingGift, resolveMapBounds, saveEventDefinition, saveEventProduce, saveGiftDefinition, saveMapSettings, scumCollections, selectGiftItems, startEvent, type RecordMap, type SCUMSurfaceData, type SCUMWorkspaceActions } from "../examples/scum-server-plugin/features/page-data.js";
|
||||
import { advanceMapPlayback, bundledVehicleImageUrl, collectMapPoints, mapPointStyle, mapVehicleCategory, playbackStartView, playbackFollowView, scumCatalogCategoryLabel, scumGiftCatalogCategory, scumGiftCatalogFilterKey, tradeGoodsTypeLabel } from "../examples/scum-server-plugin/features/page.js";
|
||||
import { scumCatalogEntries } from "../examples/scum-server-plugin/features/scum-catalog.js";
|
||||
import { scumCatalogDisplayName, scumCatalogSearchText } from "../examples/scum-server-plugin/features/scum-catalog-zh.js";
|
||||
@@ -134,9 +134,9 @@ describe("SCUM plugin feature module", () => {
|
||||
it("uses workflows as the manifest activity key and keeps activity as a compatibility alias", async () => {
|
||||
const list = vi.fn(async (collection: string) => ({ items: [{ key: `${collection}-1`, value: { collection } }], count: 1 }));
|
||||
await loadSCUMSurface({ pluginData: pluginDataActions({ list }), scum: scumActions() }, "workflows");
|
||||
expect(list.mock.calls.map(([collection]) => collection)).toEqual([scumCollections.events, scumCollections.eventProduces, scumCollections.eventRuns, scumCollections.nativeEventRounds, scumCollections.tasks, scumCollections.activityEvents, scumCollections.tradeGoods, scumCollections.tradeEvents]);
|
||||
expect(list.mock.calls.map(([collection]) => collection)).toEqual([scumCollections.events, scumCollections.eventProduces, scumCollections.eventRuns, scumCollections.mapRegions, scumCollections.mapSettings]);
|
||||
await loadSCUMSurface({ pluginData: pluginDataActions({ list }), scum: scumActions() }, "activity");
|
||||
expect(list.mock.calls.slice(-8).map(([collection]) => collection)).toEqual([scumCollections.events, scumCollections.eventProduces, scumCollections.eventRuns, scumCollections.nativeEventRounds, scumCollections.tasks, scumCollections.activityEvents, scumCollections.tradeGoods, scumCollections.tradeEvents]);
|
||||
expect(list.mock.calls.slice(-5).map(([collection]) => collection)).toEqual([scumCollections.events, scumCollections.eventProduces, scumCollections.eventRuns, scumCollections.mapRegions, scumCollections.mapSettings]);
|
||||
});
|
||||
|
||||
it("uses transaction, put, and delete for plugin-owned gift data", async () => {
|
||||
@@ -170,6 +170,8 @@ describe("SCUM plugin feature module", () => {
|
||||
const pluginData = pluginDataActions();
|
||||
const dispatch = vi.fn<NonNullable<SCUMWorkspaceActions["dispatch"]>>(async (envelope) => ({ status: "queued", result: { jobId: envelope.requestId } }));
|
||||
const actions: SCUMWorkspaceActions = { pluginData, dispatch };
|
||||
await saveEventDefinition(actions, { id: "event-list", title: "补给活动", items: [{ catalogCode: "Cash", quantity: "2", probability: "65", durability: "80" }] });
|
||||
expect(pluginData.put).toHaveBeenCalledWith(scumCollections.events, "event-list", expect.objectContaining({ title: "补给活动", items: [{ catalogCode: "Cash", quantity: 2, probability: 65, durability: 80 }] }));
|
||||
await saveEventProduce(actions, { id: "produce-1", eventId: "event-1", tradeGoodsId: "goods-1", percent: 80, value: 2, r: 100, x: 10, y: 20, z: 3 });
|
||||
expect(pluginData.put).toHaveBeenCalledWith(scumCollections.eventProduces, "event-1:produce-1", expect.objectContaining({ eventId: "event-1", tradeGoodsId: "goods-1" }));
|
||||
await startEvent(actions, { ...surfaceData.events[0], rconCommand: "#start_event event-1" }, surfaceData.eventProduces);
|
||||
@@ -268,18 +270,21 @@ describe("SCUM plugin feature module", () => {
|
||||
expect(view.texts.join("\n")).toContain("verified");
|
||||
});
|
||||
|
||||
it("renders activity definitions, status filters, runs, and records", () => {
|
||||
it("renders only a collapsed activity list and expands into area and reward configuration", () => {
|
||||
const view = renderAndCollect({ pageKey: "workflows", pageTitle: "活动管理" });
|
||||
expect(view.inputs.map((input) => input.label)).toContain("活动状态");
|
||||
expect(view.inputs.map((input) => input.label)).toEqual(expect.arrayContaining(["生成类型", "RCON 命令", "活动公告", "活动概率", "活动持续秒数", "生成物品编号", "生成半径", "生成 X", "生成 Y", "生成 Z"]));
|
||||
expect(view.inputs.map((input) => input.label)).not.toEqual(expect.arrayContaining(["RCON 命令", "活动公告", "生成物品编号", "生成 X", "生成 Y", "生成 Z"]));
|
||||
expect(view.texts).toContain("Friday Range");
|
||||
expect(view.texts).toContain("running");
|
||||
expect(view.texts).toContain("最近活动记录");
|
||||
expect(view.texts).toContain("Mira");
|
||||
expect(view.texts).toContain("活动生成项");
|
||||
expect(view.texts).toEqual(expect.arrayContaining(["物品列表", "最近商人交易", "Cargo Drop", "买入 × 2"]));
|
||||
expect(pageSource).toContain("setEventEditorOpen(detailOpen(event))");
|
||||
expect(pageSource).not.toContain("open: Boolean(view.eventId || view.eventName)");
|
||||
expect(view.texts).not.toEqual(expect.arrayContaining(["最近活动记录", "活动生成项", "物品列表", "最近商人交易", "Quest / Task", "原生赛事轮次"]));
|
||||
expect(view.buttons.map((button) => button.label)).toContain("新建活动");
|
||||
expect(pageSource).toContain("activityMapPicker(e, data, view)");
|
||||
expect(pageSource).toContain("活动物品 / NPC");
|
||||
expect(pageSource).not.toContain("setEventEditorOpen(detailOpen(event))");
|
||||
const expanded = renderAndCollect({ pageKey: "workflows", pageTitle: "活动管理", activityDetailId: "event-1" });
|
||||
expect(expanded.inputs.map((input) => input.label)).toEqual(expect.arrayContaining(["活动标题", "活动区域名称", "活动区域半径", "活动区域 X", "活动区域 Y", "活动区域 Z"]));
|
||||
expect(expanded.nodes).toContain("div:选择活动区域地图");
|
||||
expect(expanded.texts).toContain("从 SCUM 目录选择活动物品 / NPC");
|
||||
});
|
||||
|
||||
it("renders gift definitions, claims, and delivery records", () => {
|
||||
@@ -434,15 +439,18 @@ describe("SCUM plugin feature module", () => {
|
||||
expect(gifts.texts).not.toContain("从 SCUM 目录选择礼包物品");
|
||||
expect(gifts.inputs.map((input) => input.label)).not.toContain("搜索礼包物品");
|
||||
const workflows = renderAndCollect({ pageKey: "workflows", pageTitle: "活动管理" });
|
||||
expect(workflows.texts).toContain("从 SCUM 目录选择物品");
|
||||
expect(workflows.inputs.map((input) => input.label)).not.toContain("搜索生成物品");
|
||||
expect(workflows.texts).not.toContain("从 SCUM 目录选择活动物品 / NPC");
|
||||
const expanded = renderAndCollect({ pageKey: "workflows", pageTitle: "活动管理", activityDetailId: "event-1" });
|
||||
expect(expanded.texts).toContain("从 SCUM 目录选择活动物品 / NPC");
|
||||
expect(expanded.inputs.map((input) => input.label)).not.toContain("搜索生成物品");
|
||||
});
|
||||
|
||||
it("keeps gifts as an item list with probability and durability instead of command text", () => {
|
||||
expect(pageSource).toContain("礼包物品");
|
||||
expect(pageSource).toContain("礼包物品 ${itemNumber} 数量");
|
||||
expect(pageSource).toContain("礼包物品 ${itemNumber} 概率");
|
||||
expect(pageSource).toContain("礼包物品 ${itemNumber} 耐久");
|
||||
expect(pageSource).toContain("${labelPrefix} ${itemNumber} 数量");
|
||||
expect(pageSource).toContain("${labelPrefix} ${itemNumber} 概率");
|
||||
expect(pageSource).toContain("${labelPrefix} ${itemNumber} 耐久");
|
||||
expect(pageSource).toContain("activityItemEditorList");
|
||||
expect(pageSource).not.toContain("giftCommands");
|
||||
expect(pageSource).not.toContain("parseGiftCommands");
|
||||
expect(pageSource).not.toContain("礼包命令");
|
||||
@@ -591,7 +599,7 @@ function scumActions(overrides: Partial<NonNullable<SCUMWorkspaceActions["scum"]
|
||||
};
|
||||
}
|
||||
|
||||
function renderAndCollect(options: { data?: SCUMSurfaceData; permissions?: string[]; pageKey?: string; pageTitle?: string; giftTab?: "definitions" | "claims" | "deliveries" | "timed"; giftDialog?: { mode: "closed" | "view" | "edit"; gift?: RecordMap }; playerSearch?: string; pageQuery?: Record<string, string> } = {}) {
|
||||
function renderAndCollect(options: { data?: SCUMSurfaceData; permissions?: string[]; pageKey?: string; pageTitle?: string; giftTab?: "definitions" | "claims" | "deliveries" | "timed"; giftDialog?: { mode: "closed" | "view" | "edit"; gift?: RecordMap }; activityDetailId?: string; playerSearch?: string; pageQuery?: Record<string, string> } = {}) {
|
||||
const nodes: string[] = [];
|
||||
const texts: string[] = [];
|
||||
const buttons: Array<{ label: string; disabled: boolean; onClick?: () => void }> = [];
|
||||
@@ -615,6 +623,7 @@ function renderAndCollect(options: { data?: SCUMSurfaceData; permissions?: strin
|
||||
stateCall += 1;
|
||||
if (stateCall === 1) return [{ status: "ready", data: options.data ?? surfaceData } as T, () => undefined];
|
||||
if (stateCall === 3 && options.playerSearch !== undefined) return [options.playerSearch as T, () => undefined];
|
||||
if (stateCall === 9 && options.activityDetailId !== undefined) return [options.activityDetailId as T, () => undefined];
|
||||
if (options.giftDialog && typeof initial === "object" && initial !== null && "mode" in initial && (initial as { mode?: string }).mode === "closed") return [options.giftDialog as T, () => undefined];
|
||||
const value = typeof initial === "function" ? (initial as () => T)() : initial;
|
||||
if (options.giftTab && value === "definitions") return [options.giftTab as T, () => undefined];
|
||||
|
||||
Reference in New Issue
Block a user