fix: classify SCUM gift items
This commit is contained in:
@@ -39,6 +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 scumCatalogPickLimit = 48;
|
||||
const scumMapSize = 4096;
|
||||
const scumSurfaceRefreshMs = 15000;
|
||||
@@ -750,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, "可用目录选择器快速加入,格式为 目录代码:数量")), labeledField(e, "物品清单", e("input", { value: view.giftItems, "aria-label": "礼包物品", placeholder: "例如 BP_Cash_01:2, Water-Bottle.01: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 物品分类筛选;格式为 目录代码:数量")), 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 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 }, "保存礼包"))
|
||||
)
|
||||
@@ -1361,9 +1362,13 @@ function mapLayerSetting(settings: RecordMap | undefined): ScumMapLayer {
|
||||
|
||||
function mapLayerLabel(layer: ScumMapLayer): string { return layer === "topo" ? "等高线" : layer === "night" ? "夜间" : "地形"; }
|
||||
|
||||
function catalogMatches(query: string, kind: string): ScumCatalogEntry[] {
|
||||
function catalogMatches(query: string, kind: string, target: CatalogPickTarget): ScumCatalogEntry[] {
|
||||
const search = query.trim().toLowerCase();
|
||||
return scumCatalogEntries.filter((entry) => (kind === "all" || entry.kind === kind) && (!search || entry.name.toLowerCase().includes(search) || entry.code.toLowerCase().includes(search) || entry.command.toLowerCase().includes(search))).slice(0, scumCatalogPickLimit);
|
||||
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;
|
||||
return matchesKind && (!search || entry.name.toLowerCase().includes(search) || entry.code.toLowerCase().includes(search) || entry.command.toLowerCase().includes(search));
|
||||
}).slice(0, scumCatalogPickLimit);
|
||||
}
|
||||
|
||||
function appendCatalogItem(current: string, code: string): string {
|
||||
@@ -1390,17 +1395,35 @@ function giftCatalogIcons(gift: RecordMap): string[] {
|
||||
}
|
||||
|
||||
function catalogPicker(e: ReactLike["createElement"], view: ViewState, target: CatalogPickTarget) {
|
||||
const matches = catalogMatches(view.catalogSearch, view.catalogKind);
|
||||
const filters = target === "gift-items" ? scumGiftCatalogCategories : scumCatalogKinds;
|
||||
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" }, "图标 / 名称 / 代码")),
|
||||
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": "目录类型" }, scumCatalogKinds.map(([key, label]) => e("button", { key, type: "button", className: view.catalogKind === key ? "icon-command is-active" : "icon-command", "aria-pressed": view.catalogKind === key, onClick: () => view.setCatalogKind(key) }, label))),
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
export function scumGiftCatalogCategory(entry: ScumCatalogEntry): string {
|
||||
const icon = entry.icon.toLowerCase();
|
||||
if (icon.includes("/weapons/") || icon.includes("/crafting/traps/")) return "weapons";
|
||||
if (icon.includes("/fishing/")) return "fishing";
|
||||
if (icon.includes("/clothes/")) return "gear";
|
||||
if (icon.includes("/food/")) return "food";
|
||||
if (icon.includes("/medicals/")) return "medical";
|
||||
if (icon.includes("/crafting/basebuilding/")) return "blueprints";
|
||||
if (icon.includes("/crafting/components/") || icon.includes("/misc/components/")) return "components";
|
||||
if (icon.includes("/crafting/tools/") || icon.includes("/misc/tools/")) return "tools";
|
||||
if (icon.includes("/farming/")) return "farming";
|
||||
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)] ?? "其他"; }
|
||||
|
||||
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); }
|
||||
@@ -1410,7 +1433,19 @@ function resettableGiftPanel(e: ReactLike["createElement"], title: string, rows:
|
||||
function itemCatalogPanel(e: ReactLike["createElement"], rows: RecordMap[]) { return tablePanel(e, "物品列表", recentRows(rows, "lastSeenAt", "updatedAt", "createdAt"), (item) => [tradeGoodsName(item), tradeGoodsTypeLabel(item), textField(item, "code", "itemCode", "className") || "unknown", dateField(item, "lastSeenAt", "updatedAt", "createdAt")]); }
|
||||
function tradeGoodsLabel(rows: RecordMap[], code: string): string { const item = tradeGoodsIndex(rows).get(code) ?? tradeGoodsIndex(rows).get(code.replace(/^#spawnvehicle\s+/i, "")); return item ? `${tradeGoodsName(item)} (${code})` : code || "unknown"; }
|
||||
function tradeGoodsName(item: RecordMap): string { return textField(item, "nameCn", "name_cn", "name", "className") || textField(item, "code", "itemCode") || "未命名物品"; }
|
||||
function tradeGoodsTypeLabel(item: RecordMap): string { return textField(item, "typeName", "type_name") || (textField(item, "type") === "21" || isVehicleCatalogItem(item) ? "其他载具" : "未知类型"); }
|
||||
export function tradeGoodsTypeLabel(item: RecordMap): string {
|
||||
const code = textField(item, "code", "itemCode", "className", "spawnCommand");
|
||||
const entry = scumCatalogEntryFor(code);
|
||||
if (entry?.kind === "item") return scumCatalogCategoryLabel(entry);
|
||||
if (entry?.kind === "vehicle") return "载具";
|
||||
if (entry?.kind === "zombie") return "丧尸";
|
||||
if (entry?.kind === "animal") return "动物";
|
||||
if (entry?.kind === "armed-npc") return "NPC";
|
||||
if (isVehicleCatalogItem(item)) return "载具";
|
||||
const typeName = textField(item, "typeName", "type_name");
|
||||
if (typeName && !/^(?:item|物品|unknown|未知(?:类型)?|undefined|null)$/i.test(typeName.trim())) return typeName;
|
||||
return textField(item, "type") === "21" || isVehicleCatalogItem(item) ? "其他载具" : "未知类型";
|
||||
}
|
||||
function tradeActionLabel(event: RecordMap): string { const value = textField(event, "tradeVerb", "tradeKind", "action").toLowerCase(); return value === "purchased" || value === "purchase" ? "买入" : value === "sold" || value === "sale" ? "卖出" : value || "交易"; }
|
||||
function tradeGoodsIndex(rows: RecordMap[]): Map<string, RecordMap> { const result = new Map<string, RecordMap>(); for (const row of rows) for (const key of [textField(row, "code"), textField(row, "itemCode"), textField(row, "className")].filter(Boolean)) result.set(key, row); return result; }
|
||||
function recentRows(rows: RecordMap[], ...keys: string[]): RecordMap[] { return [...rows].sort((left, right) => rowTime(right, keys) - rowTime(left, keys)).slice(0, 24); }
|
||||
|
||||
Reference in New Issue
Block a user