diff --git a/src/app/admin/import/audit/audit-client.tsx b/src/app/admin/import/audit/audit-client.tsx index cc3e6f92d0..ae2817dcf6 100644 --- a/src/app/admin/import/audit/audit-client.tsx +++ b/src/app/admin/import/audit/audit-client.tsx @@ -86,7 +86,11 @@ interface AuditEvent { sqlCount?: number; applied?: number; sqlFailed?: number; - furniData?: { added: number; removedDuplicates: number }; + furniData?: { + added: number; + removedDuplicates: number; + removedIdConflicts: number; + }; structure?: { orphanedRemoved: number; orphanedCleaned: number; @@ -175,6 +179,7 @@ export function AuditClient() { const [furniDataResult, setFurniDataResult] = useState<{ added: number; removedDuplicates: number; + removedIdConflicts: number; } | null>(null); const [structureResult, setStructureResult] = useState<{ orphanedRemoved: number; @@ -675,6 +680,15 @@ export function AuditClient() { )} + {furniDataResult.removedIdConflicts > 0 && ( + <> + {" · "} + + {furniDataResult.removedIdConflicts} spriteId conflicts + fixed + + + )} )} diff --git a/src/lib/services/catalog-audit.ts b/src/lib/services/catalog-audit.ts index d3bfd7dc91..18b3ed13b5 100644 --- a/src/lib/services/catalog-audit.ts +++ b/src/lib/services/catalog-audit.ts @@ -56,7 +56,11 @@ export interface AuditEvent { sqlCount?: number; applied?: number; sqlFailed?: number; - furniData?: { added: number; removedDuplicates: number }; + furniData?: { + added: number; + removedDuplicates: number; + removedIdConflicts: number; + }; structure?: { orphanedRemoved: number; orphanedCleaned: number; @@ -715,6 +719,7 @@ export async function runCatalogAudit( furniData: { added: result.added, removedDuplicates: result.removedDuplicates, + removedIdConflicts: result.removedIdConflicts, }, }); } catch (err) { diff --git a/src/lib/services/catalog-repair.ts b/src/lib/services/catalog-repair.ts index 832f70171d..20c74d34a4 100644 --- a/src/lib/services/catalog-repair.ts +++ b/src/lib/services/catalog-repair.ts @@ -620,6 +620,7 @@ export async function repairDuplicateClassnames(): Promise, ): Promise { - if (items.length === 0) return { added: 0, removedDuplicates: 0 }; + if (items.length === 0) + return { added: 0, removedDuplicates: 0, removedIdConflicts: 0 }; return withFurniDataLock(async () => { const furniData = (await readFurniData()) as Record< @@ -697,7 +700,33 @@ export async function repairFurniData( } } - // 2) Add entries for items_base rows missing from the file. + // 2) Resolve spriteId conflicts (same id used by different classnames) — keep first, remove others. + let removedIdConflicts = 0; + for (const section of ["roomitemtypes", "wallitemtypes"] as const) { + const sec = furniData[section]; + if (!sec?.furnitype) continue; + const idToClassname = new Map(); + const deduped = sec.furnitype.filter((e) => { + const id = Number(e?.id); + const classname = typeof e.classname === "string" ? e.classname : ""; + if (!Number.isFinite(id) || id <= 0 || !classname) return true; + const existing = idToClassname.get(id); + if (existing && existing !== classname) { + // Conflict: same id used by different classname — keep first, remove this + removedIdConflicts++; + return false; + } + if (!existing) { + idToClassname.set(id, classname); + } + return true; + }); + if (deduped.length !== sec.furnitype.length) { + furniData[section].furnitype = deduped; + } + } + + // 3) Add entries for items_base rows missing from the file. const existing = new Set(); for (const section of ["roomitemtypes", "wallitemtypes"] as const) { for (const e of furniData[section]?.furnitype ?? []) { @@ -714,8 +743,8 @@ export async function repairFurniData( }); } - // 3) Write once if anything changed. - if (removedDuplicates > 0 || added.length > 0) { + // 4) Write once if anything changed. + if (removedDuplicates > 0 || removedIdConflicts > 0 || added.length > 0) { for (const { entry, itemType } of added) { const sectionKey = itemType === "i" ? "wallitemtypes" : "roomitemtypes"; if (!furniData[sectionKey]) { @@ -725,6 +754,6 @@ export async function repairFurniData( } await writeFurniData(furniData as Record); } - return { added: added.length, removedDuplicates }; + return { added: added.length, removedDuplicates, removedIdConflicts }; }); }