Reconcile FurnitureData.json with items_base after import
After every single import, batch import and batch-regen, cross-check all items_base rows against the local FurnitureData.json: entries whose spriteId drifted are corrected to the DB id, and missing entries plus real id conflicts are reported in the API/SSE response. Entries that are missing entirely are counted (rebuilding them needs SWF/nitro metadata).
This commit is contained in:
1 parent
b4021f6b42
commit
b4968a9967
4 files changed
+118
-1
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { existsSync, promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { db, ItemsBase } from "@/lib/db";
|
||||
import {
|
||||
getGamedataRoot,
|
||||
getNitroFilesRoot,
|
||||
@@ -442,3 +443,86 @@ export async function appendFurniEntriesBatch(
|
||||
await writeFurniData(furniData as Record<string, unknown>);
|
||||
});
|
||||
}
|
||||
|
||||
export interface FurniDataReconcileResult {
|
||||
/** Entries whose stale spriteId was corrected to the DB id. */
|
||||
fixedIds: number;
|
||||
/** items_base rows with no matching furnitype entry (need re-import). */
|
||||
missing: number;
|
||||
/** spriteIds used by a different classname in the file; left untouched. */
|
||||
conflicts: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-check every items_base row against the local FurnitureData.json and
|
||||
* repair drift:
|
||||
* - an entry matching the classname but carrying a stale spriteId is updated
|
||||
* to the DB id (items_base is authoritative);
|
||||
* - rows missing entirely from the file are counted (they need a re-import
|
||||
* or batch-regen, since building an entry requires the SWF/nitro metadata);
|
||||
* - genuine spriteId conflicts (id used by a different classname) are counted
|
||||
* but never auto-fixed.
|
||||
*/
|
||||
export async function reconcileFurniDataWithItemsBase(): Promise<FurniDataReconcileResult> {
|
||||
return withFurniDataLock(async () => {
|
||||
const rows = await db
|
||||
.select({ id: ItemsBase.id, itemName: ItemsBase.itemName })
|
||||
.from(ItemsBase);
|
||||
|
||||
const furniData = (await readFurniData()) as Record<
|
||||
string,
|
||||
{ furnitype: Array<Record<string, unknown>> }
|
||||
>;
|
||||
|
||||
const byId = new Map<
|
||||
number,
|
||||
{ section: string; index: number; entry: Record<string, unknown> }
|
||||
>();
|
||||
const byClass = new Map<
|
||||
string,
|
||||
{ section: string; index: number; entry: Record<string, unknown> }
|
||||
>();
|
||||
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
|
||||
const arr = furniData[section]?.furnitype ?? [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const e = arr[i];
|
||||
const id = Number(e?.id);
|
||||
if (Number.isFinite(id) && id > 0 && !byId.has(id)) {
|
||||
byId.set(id, { section, index: i, entry: e });
|
||||
}
|
||||
const cn = typeof e?.classname === "string" ? e.classname : "";
|
||||
if (cn && !byClass.has(cn)) {
|
||||
byClass.set(cn, { section, index: i, entry: e });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fixedIds = 0;
|
||||
let missing = 0;
|
||||
let conflicts = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
const spriteId = row.id;
|
||||
const existing = byId.get(spriteId);
|
||||
if (existing) {
|
||||
if (existing.entry.classname !== row.itemName) {
|
||||
conflicts++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const byName = byClass.get(row.itemName);
|
||||
if (byName) {
|
||||
byName.entry.id = spriteId;
|
||||
byId.set(spriteId, byName);
|
||||
fixedIds++;
|
||||
} else {
|
||||
missing++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fixedIds > 0) {
|
||||
await writeFurniData(furniData as Record<string, unknown>);
|
||||
}
|
||||
return { fixedIds, missing, conflicts };
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user