Fix FurnitureData.json spriteId conflict auto-repair
- Added spriteId conflict resolution to repairFurniData(): keeps first classname per id, removes conflicting entries - Returns new removedIdConflicts count in repair result - Updated audit event type and client UI to display removedIdConflicts - Updated catalog-audit.ts event type and client state type - When 'Repair FurnitureData.json' is checked, it now fixes id conflicts automatically
This commit is contained in:
1 parent
3b6ebe7bdc
commit
f89b8a6adf
3 files changed
+56
-8
No files matched your search
@@ -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() {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{furniDataResult.removedIdConflicts > 0 && (
|
||||
<>
|
||||
{" · "}
|
||||
<span className="text-[var(--admin-warning)] font-medium">
|
||||
{furniDataResult.removedIdConflicts} spriteId conflicts
|
||||
fixed
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -620,6 +620,7 @@ export async function repairDuplicateClassnames(): Promise<DuplicateClassnameRes
|
||||
export interface FurniDataRepairResult {
|
||||
added: number;
|
||||
removedDuplicates: number;
|
||||
removedIdConflicts: number;
|
||||
}
|
||||
|
||||
function buildMinimalFurniEntry(item: {
|
||||
@@ -658,7 +659,8 @@ function buildMinimalFurniEntry(item: {
|
||||
|
||||
/**
|
||||
* Add FurnitureData.json entries for items_base rows that are missing from the
|
||||
* file, and remove duplicate classnames (keeping the first occurrence).
|
||||
* file, remove duplicate classnames, and resolve spriteId conflicts
|
||||
* (keeping the first classname per id, removing conflicting ones).
|
||||
*/
|
||||
export async function repairFurniData(
|
||||
items: Array<{
|
||||
@@ -668,7 +670,8 @@ export async function repairFurniData(
|
||||
type: string;
|
||||
}>,
|
||||
): Promise<FurniDataRepairResult> {
|
||||
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<number, string>();
|
||||
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<string>();
|
||||
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<string, unknown>);
|
||||
}
|
||||
return { added: added.length, removedDuplicates };
|
||||
return { added: added.length, removedDuplicates, removedIdConflicts };
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user