fix: support mixed SCUM gift catalog entries
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { scumCatalogEntryFor } from "./scum-catalog.js";
|
||||
|
||||
export type RecordMap = Record<string, unknown>;
|
||||
|
||||
export type PluginDataMutation = { operation: "put" | "delete"; key: string; value?: RecordMap };
|
||||
@@ -160,7 +162,7 @@ export async function queueGiftDelivery(actions: SCUMWorkspaceActions, gift: Rec
|
||||
if (!items.length && !operations.length) throw new Error("礼包必须包含物品或命令。");
|
||||
const now = Date.now();
|
||||
const grantId = safeCommandId(`gift:${giftCode}:${playerId}:${now}`);
|
||||
const commands = [...items.map((item) => `#SpawnItem ${item.catalogCode} ${item.quantity}`), ...operations].map(normalizeRCONCommand);
|
||||
const commands = [...giftCatalogCommands(items), ...operations].map(normalizeRCONCommand);
|
||||
const results = [];
|
||||
for (const [index, command] of commands.entries()) {
|
||||
results.push(await dispatchSCUMRCONCommand(actions, command, `${grantId}:${index + 1}`));
|
||||
@@ -216,6 +218,14 @@ export function parseGiftItems(input: string): Array<{ catalogCode: string; quan
|
||||
return items;
|
||||
}
|
||||
|
||||
function giftCatalogCommands(items: Array<{ catalogCode: string; quantity: number }>): string[] {
|
||||
return items.flatMap((item) => {
|
||||
const entry = scumCatalogEntryFor(item.catalogCode);
|
||||
if (!entry || entry.kind === "item") return [`#SpawnItem ${item.catalogCode} ${item.quantity}`];
|
||||
return Array.from({ length: item.quantity }, () => entry.command);
|
||||
});
|
||||
}
|
||||
|
||||
export function parseGiftCommands(input: string): Array<{ command: string }> {
|
||||
return input.split(/\r?\n/).map((command) => command.trim()).filter(Boolean).map((command) => ({ command }));
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ const scumMapBaseLayers: ReadonlyArray<readonly [ScumMapLayer, string]> = [["ter
|
||||
const scumMapVehicleFilters: ReadonlyArray<readonly [MapVehicleFilter, string]> = [["all", "全部载具"], ["land", "陆地载具"], ["water", "水上载具"], ["air", "飞行类载具"], ["other", "其他载具"]];
|
||||
const scumFlightVehicleKeys = new Set(["kinglet_duster", "kinglet_mariner", "kinglet_scout"]);
|
||||
const scumCatalogKinds: ReadonlyArray<readonly [string, string]> = [["all", "全部"], ["item", "物品"], ["vehicle", "载具"], ["zombie", "丧尸"], ["animal", "动物"], ["armed-npc", "NPC"]];
|
||||
const scumGiftCatalogCategories: ReadonlyArray<readonly [string, string]> = [["all", "全部物品"], ["weapons", "武器"], ["fishing", "钓鱼"], ["gear", "装备"], ["food", "食物"], ["medical", "医疗"], ["components", "组件"], ["blueprints", "蓝图"], ["tools", "工具"], ["farming", "农业"], ["misc", "其他"]];
|
||||
const scumGiftCatalogCategories: ReadonlyArray<readonly [string, string]> = [["all", "全部目录"], ["weapons", "武器"], ["fishing", "钓鱼"], ["gear", "装备"], ["food", "食物"], ["medical", "医疗"], ["components", "组件"], ["blueprints", "蓝图"], ["tools", "工具"], ["farming", "农业"], ["misc", "其他"], ["vehicle", "载具"], ["animal", "动物"], ["zombie", "丧尸"], ["armed-npc", "NPC"]];
|
||||
const scumCatalogPickLimit = 48;
|
||||
const scumMapSize = 4096;
|
||||
const scumSurfaceRefreshMs = 15000;
|
||||
@@ -751,7 +751,7 @@ function giftEditorDialog(e: ReactLike["createElement"], view: ViewState, action
|
||||
labeledField(e, "发放次数", e("input", { value: view.giftNumber, "aria-label": "发放次数", type: "number", min: "1", placeholder: "1", onChange: (event: InputEvent) => view.setGiftNumber(inputValue(event)) })),
|
||||
labeledField(e, "成就类型", e("input", { value: view.giftAchievement, "aria-label": "成就类型", type: "number", min: "0", placeholder: "0", onChange: (event: InputEvent) => view.setGiftAchievement(inputValue(event)) })),
|
||||
labeledField(e, "成就值", e("input", { value: view.giftAchievementNumber, "aria-label": "成就值", type: "number", min: "0", placeholder: "0", onChange: (event: InputEvent) => view.setGiftAchievementNumber(inputValue(event)) })),
|
||||
e("div", { className: "gift-form-wide gift-form-section" }, e("div", { className: "gift-form-section-title" }, e("strong", null, "礼包物品"), e("span", null, "按 SCUM 物品分类筛选;格式为 目录代码:数量")), labeledField(e, "物品清单", e("input", { value: view.giftItems, "aria-label": "礼包物品", placeholder: "例如 Aloe_Vera:2, BakedBeans:1", onChange: (event: InputEvent) => view.setGiftItems(inputValue(event)) })), catalogPicker(e, view, "gift-items")),
|
||||
e("div", { className: "gift-form-wide gift-form-section" }, e("div", { className: "gift-form-section-title" }, e("strong", null, "礼包物品"), e("span", null, "按 SCUM 目录分类筛选(物品、载具、动物、丧尸、NPC);格式为 目录代码:数量")), labeledField(e, "物品清单", e("input", { value: view.giftItems, "aria-label": "礼包物品", placeholder: "例如 Aloe_Vera:2, BPC_Laika:1, BP_Bear2:2", onChange: (event: InputEvent) => view.setGiftItems(inputValue(event)) })), catalogPicker(e, view, "gift-items")),
|
||||
e("div", { className: "gift-form-wide gift-form-section" }, e("div", { className: "gift-form-section-title" }, e("strong", null, "礼包命令"), e("span", null, "每行一条 SCUM RCON 命令,可选")), labeledField(e, "命令清单", e("textarea", { value: view.giftCommands, "aria-label": "礼包命令", placeholder: "例如 #announce Welcome\n每行一条命令", onChange: (event: InputEvent) => view.setGiftCommands(inputValue(event)) }))),
|
||||
e("div", { className: "confirm-actions gift-dialog-actions" }, e("button", { type: "button", onClick: () => view.setGiftDialog({ mode: "closed" }) }, "取消"), e("button", { type: "button", className: "confirm-primary", disabled: !actions?.pluginData, onClick: saveGift }, "保存礼包"))
|
||||
)
|
||||
@@ -1365,8 +1365,7 @@ function mapLayerLabel(layer: ScumMapLayer): string { return layer === "topo" ?
|
||||
function catalogMatches(query: string, kind: string, target: CatalogPickTarget): ScumCatalogEntry[] {
|
||||
const search = query.trim().toLowerCase();
|
||||
return scumCatalogEntries.filter((entry) => {
|
||||
if (target === "gift-items" && entry.kind !== "item") return false;
|
||||
const matchesKind = target === "gift-items" ? kind === "all" || scumGiftCatalogCategory(entry) === kind : kind === "all" || entry.kind === kind;
|
||||
const matchesKind = target === "gift-items" ? kind === "all" || scumGiftCatalogFilterKey(entry) === kind : kind === "all" || entry.kind === kind;
|
||||
return matchesKind && (!search || entry.name.toLowerCase().includes(search) || entry.code.toLowerCase().includes(search) || entry.command.toLowerCase().includes(search));
|
||||
}).slice(0, scumCatalogPickLimit);
|
||||
}
|
||||
@@ -1399,10 +1398,10 @@ function catalogPicker(e: ReactLike["createElement"], view: ViewState, target: C
|
||||
const activeFilter = filters.some(([key]) => key === view.catalogKind) ? view.catalogKind : "all";
|
||||
const matches = catalogMatches(view.catalogSearch, activeFilter, target);
|
||||
return e("details", { className: "console-module scum-editor scum-catalog-picker", open: view.catalogOpen, onToggle: (event: InputEvent) => view.setCatalogOpen(detailOpen(event)) },
|
||||
e("summary", null, e("strong", null, target === "gift-items" ? "从 SCUM 目录选择礼包物品" : "从 SCUM 目录选择物品"), e("span", { className: "page-status" }, "图标 / 名称 / 代码")),
|
||||
e("summary", null, e("strong", null, target === "gift-items" ? "从 SCUM 目录选择礼包内容" : "从 SCUM 目录选择物品"), e("span", { className: "page-status" }, "图标 / 名称 / 代码")),
|
||||
view.catalogOpen ? e("div", { className: "scum-catalog-picker-body" },
|
||||
e("input", { type: "search", value: view.catalogSearch, "aria-label": target === "gift-items" ? "搜索礼包物品" : "搜索生成物品", placeholder: "名称 / 代码,例如 Laika", onChange: (event: InputEvent) => view.setCatalogSearch(inputValue(event)) }),
|
||||
e("div", { className: "scum-catalog-kinds", role: "group", "aria-label": target === "gift-items" ? "礼包物品分类" : "目录类型" }, filters.map(([key, label]) => e("button", { key, type: "button", className: activeFilter === key ? "icon-command is-active" : "icon-command", "aria-pressed": activeFilter === key, onClick: () => view.setCatalogKind(key) }, label))),
|
||||
e("input", { type: "search", value: view.catalogSearch, "aria-label": target === "gift-items" ? "搜索礼包目录" : "搜索生成物品", placeholder: "名称 / 代码,例如 Laika", onChange: (event: InputEvent) => view.setCatalogSearch(inputValue(event)) }),
|
||||
e("div", { className: "scum-catalog-kinds", role: "group", "aria-label": target === "gift-items" ? "礼包目录分类" : "目录类型" }, filters.map(([key, label]) => e("button", { key, type: "button", className: activeFilter === key ? "icon-command is-active" : "icon-command", "aria-pressed": activeFilter === key, onClick: () => view.setCatalogKind(key) }, label))),
|
||||
e("div", { className: "scum-catalog-grid" }, matches.length ? matches.map((entry) => { const icon = scumCatalogIconUrl(entry.icon); return e("button", { key: entry.code, type: "button", className: "scum-catalog-chip", title: `${entry.name} · ${entry.command}`, onClick: () => target === "gift-items" ? view.setGiftItems(appendCatalogItem(view.giftItems, entry.code)) : view.setProduceTradeGoodsId(entry.code) }, icon ? e("img", { className: "scum-catalog-icon", src: icon, alt: "" }) : e("span", { className: "scum-catalog-icon scum-catalog-icon-empty" }), e("span", { className: "scum-catalog-meta" }, e("strong", null, entry.name), e("span", { className: "provider-id" }, entry.code))); }) : e("p", { className: "page-status" }, "没有匹配的目录条目。"))
|
||||
) : null
|
||||
);
|
||||
@@ -1422,8 +1421,9 @@ export function scumGiftCatalogCategory(entry: ScumCatalogEntry): string {
|
||||
return "misc";
|
||||
}
|
||||
|
||||
export function scumCatalogCategoryLabel(entry: ScumCatalogEntry): string { return ({ weapons: "武器", fishing: "钓鱼", gear: "装备", food: "食物", medical: "医疗", components: "组件", blueprints: "蓝图", tools: "工具", farming: "农业", misc: "其他" } as Record<string, string>)[scumGiftCatalogCategory(entry)] ?? "其他"; }
|
||||
export function scumGiftCatalogFilterKey(entry: ScumCatalogEntry): string { return entry.kind === "item" ? scumGiftCatalogCategory(entry) : entry.kind; }
|
||||
|
||||
export function scumCatalogCategoryLabel(entry: ScumCatalogEntry): string { if (entry.kind === "vehicle") return "载具"; if (entry.kind === "animal") return "动物"; if (entry.kind === "zombie") return "丧尸"; if (entry.kind === "armed-npc") return "NPC"; return ({ weapons: "武器", fishing: "钓鱼", gear: "装备", food: "食物", medical: "医疗", components: "组件", blueprints: "蓝图", tools: "工具", farming: "农业", misc: "其他" } as Record<string, string>)[scumGiftCatalogCategory(entry)] ?? "其他"; }
|
||||
function labeledField(e: ReactLike["createElement"], label: string, control: unknown) { return e("label", { className: "console-field" }, e("span", null, label), control); }
|
||||
function detailOpen(event: InputEvent): boolean { return Boolean(event.target?.open); }
|
||||
function giftTabButton(e: ReactLike["createElement"], view: ViewState, tab: GiftTab, label: string) { return e("button", { type: "button", role: "tab", "aria-selected": view.giftTab === tab, className: view.giftTab === tab ? "primary-command" : "icon-command", onClick: () => view.setGiftTab(tab) }, label); }
|
||||
|
||||
Reference in New Issue
Block a user