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;
|
sqlCount?: number;
|
||||||
applied?: number;
|
applied?: number;
|
||||||
sqlFailed?: number;
|
sqlFailed?: number;
|
||||||
furniData?: { added: number; removedDuplicates: number };
|
furniData?: {
|
||||||
|
added: number;
|
||||||
|
removedDuplicates: number;
|
||||||
|
removedIdConflicts: number;
|
||||||
|
};
|
||||||
structure?: {
|
structure?: {
|
||||||
orphanedRemoved: number;
|
orphanedRemoved: number;
|
||||||
orphanedCleaned: number;
|
orphanedCleaned: number;
|
||||||
@@ -175,6 +179,7 @@ export function AuditClient() {
|
|||||||
const [furniDataResult, setFurniDataResult] = useState<{
|
const [furniDataResult, setFurniDataResult] = useState<{
|
||||||
added: number;
|
added: number;
|
||||||
removedDuplicates: number;
|
removedDuplicates: number;
|
||||||
|
removedIdConflicts: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [structureResult, setStructureResult] = useState<{
|
const [structureResult, setStructureResult] = useState<{
|
||||||
orphanedRemoved: number;
|
orphanedRemoved: number;
|
||||||
@@ -675,6 +680,15 @@ export function AuditClient() {
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{furniDataResult.removedIdConflicts > 0 && (
|
||||||
|
<>
|
||||||
|
{" · "}
|
||||||
|
<span className="text-[var(--admin-warning)] font-medium">
|
||||||
|
{furniDataResult.removedIdConflicts} spriteId conflicts
|
||||||
|
fixed
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -56,7 +56,11 @@ export interface AuditEvent {
|
|||||||
sqlCount?: number;
|
sqlCount?: number;
|
||||||
applied?: number;
|
applied?: number;
|
||||||
sqlFailed?: number;
|
sqlFailed?: number;
|
||||||
furniData?: { added: number; removedDuplicates: number };
|
furniData?: {
|
||||||
|
added: number;
|
||||||
|
removedDuplicates: number;
|
||||||
|
removedIdConflicts: number;
|
||||||
|
};
|
||||||
structure?: {
|
structure?: {
|
||||||
orphanedRemoved: number;
|
orphanedRemoved: number;
|
||||||
orphanedCleaned: number;
|
orphanedCleaned: number;
|
||||||
@@ -715,6 +719,7 @@ export async function runCatalogAudit(
|
|||||||
furniData: {
|
furniData: {
|
||||||
added: result.added,
|
added: result.added,
|
||||||
removedDuplicates: result.removedDuplicates,
|
removedDuplicates: result.removedDuplicates,
|
||||||
|
removedIdConflicts: result.removedIdConflicts,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -620,6 +620,7 @@ export async function repairDuplicateClassnames(): Promise<DuplicateClassnameRes
|
|||||||
export interface FurniDataRepairResult {
|
export interface FurniDataRepairResult {
|
||||||
added: number;
|
added: number;
|
||||||
removedDuplicates: number;
|
removedDuplicates: number;
|
||||||
|
removedIdConflicts: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMinimalFurniEntry(item: {
|
function buildMinimalFurniEntry(item: {
|
||||||
@@ -658,7 +659,8 @@ function buildMinimalFurniEntry(item: {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add FurnitureData.json entries for items_base rows that are missing from the
|
* 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(
|
export async function repairFurniData(
|
||||||
items: Array<{
|
items: Array<{
|
||||||
@@ -668,7 +670,8 @@ export async function repairFurniData(
|
|||||||
type: string;
|
type: string;
|
||||||
}>,
|
}>,
|
||||||
): Promise<FurniDataRepairResult> {
|
): 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 () => {
|
return withFurniDataLock(async () => {
|
||||||
const furniData = (await readFurniData()) as Record<
|
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>();
|
const existing = new Set<string>();
|
||||||
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
|
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
|
||||||
for (const e of furniData[section]?.furnitype ?? []) {
|
for (const e of furniData[section]?.furnitype ?? []) {
|
||||||
@@ -714,8 +743,8 @@ export async function repairFurniData(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Write once if anything changed.
|
// 4) Write once if anything changed.
|
||||||
if (removedDuplicates > 0 || added.length > 0) {
|
if (removedDuplicates > 0 || removedIdConflicts > 0 || added.length > 0) {
|
||||||
for (const { entry, itemType } of added) {
|
for (const { entry, itemType } of added) {
|
||||||
const sectionKey = itemType === "i" ? "wallitemtypes" : "roomitemtypes";
|
const sectionKey = itemType === "i" ? "wallitemtypes" : "roomitemtypes";
|
||||||
if (!furniData[sectionKey]) {
|
if (!furniData[sectionKey]) {
|
||||||
@@ -725,6 +754,6 @@ export async function repairFurniData(
|
|||||||
}
|
}
|
||||||
await writeFurniData(furniData as Record<string, unknown>);
|
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