feat: add auto furniture interaction detection
- New auto-interaction.ts: detects canSit/canLay/canStand from classname/name keywords - Maps keywords to interaction types (bench, chair, bed, table, etc.) - Returns clickLimit and interactionModesCount for proper click/interaction limits - Nitro editor: added auto-detect button with preview of detected settings - furni-import.ts: auto-sets interaction_modes_count during import - upload-import.ts: auto-detects for direct insert and SQL migrations - clone-import.ts: auto-detects when cloning from other hotels
This commit is contained in:
1 parent
0b4fc4559e
commit
dee843106f
8 files changed
+596
-25
No files matched your search
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { eq, inArray, sql } from "drizzle-orm";
|
||||
import { db, ItemsBase } from "@/lib/db";
|
||||
import { autoDetectInteraction } from "@/lib/furni/auto-interaction";
|
||||
import { HABBO_GAMEDATA_HOTEL_SETTING_KEY } from "@/lib/habbo-gamedata-hotel";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { extractFurniIconPng } from "@/lib/services/clone-icon";
|
||||
@@ -324,16 +325,21 @@ export async function cloneSingleFurni(params: {
|
||||
// allow_stack: derived from stackHeight (mirrors furni-import.ts `dims.z > 0`).
|
||||
// allow_walk = canstandon, allow_sit = cansiton, allow_lay = canlayon.
|
||||
const allowStack = stackHeight > 0 ? "1" : "0";
|
||||
|
||||
// Auto-detect interaction modes count from classname/name
|
||||
const autoInteraction = autoDetectInteraction(classname, entry.name);
|
||||
const interactionModesCount = autoInteraction.interactionModesCount;
|
||||
|
||||
let newId: number;
|
||||
try {
|
||||
newId = await allocateItemsBaseId(async (nextId) => {
|
||||
await db.execute(sql`
|
||||
INSERT INTO items_base
|
||||
(id, sprite_id, public_name, item_name, type, width, length, stack_height,
|
||||
allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, customparams)
|
||||
allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, interaction_modes_count, customparams)
|
||||
VALUES
|
||||
(${nextId}, ${nextId}, ${entry.name}, ${classname}, ${itemType}, ${entry.xdim}, ${entry.ydim}, ${stackHeight},
|
||||
${allowStack}, ${entry.cansiton ? "1" : "0"}, ${entry.canlayon ? "1" : "0"}, ${entry.canstandon ? "1" : "0"}, 'default', ${entry.customparams})`);
|
||||
${allowStack}, ${entry.cansiton ? "1" : "0"}, ${entry.canlayon ? "1" : "0"}, ${entry.canstandon ? "1" : "0"}, 'default', ${interactionModesCount}, ${entry.customparams})`);
|
||||
return nextId;
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -59,6 +59,7 @@ export const IMPORTED_PAGE_CAPTION = "Imported Furniture";
|
||||
export { CATEGORY_PAGE, CLASSNAME_RULES } from "@/lib/furni/auto-catalog";
|
||||
|
||||
import { CATEGORY_PAGE } from "@/lib/furni/auto-catalog";
|
||||
import { autoDetectInteraction } from "@/lib/furni/auto-interaction";
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -117,7 +118,9 @@ export function buildItemsBaseEnrichmentUpdate(params: {
|
||||
canstandon: boolean;
|
||||
allowStack: string;
|
||||
interactionType: string;
|
||||
interactionModesCount?: number;
|
||||
}): SQL {
|
||||
const modesCount = params.interactionModesCount ?? 0;
|
||||
return sql`
|
||||
UPDATE items_base
|
||||
SET width = ${params.dims.x}, length = ${params.dims.y}, stack_height = ${params.dims.z},
|
||||
@@ -125,7 +128,8 @@ export function buildItemsBaseEnrichmentUpdate(params: {
|
||||
allow_lay = ${params.canlayon ? "1" : "0"},
|
||||
allow_walk = ${params.canstandon ? "1" : "0"},
|
||||
allow_stack = ${params.allowStack},
|
||||
interaction_type = ${params.interactionType}
|
||||
interaction_type = ${params.interactionType},
|
||||
interaction_modes_count = ${modesCount}
|
||||
WHERE id = ${params.id}
|
||||
`;
|
||||
}
|
||||
@@ -864,6 +868,10 @@ export async function importSingleFurni(params: {
|
||||
// allow_stack: if it has height and you can put things on it
|
||||
const allowStack = dims.z > 0 ? "1" : "0";
|
||||
|
||||
// Auto-detect interaction modes count from classname/name
|
||||
const autoInteraction = autoDetectInteraction(classname, bestName);
|
||||
const interactionModesCount = autoInteraction.interactionModesCount;
|
||||
|
||||
// Update items_base with ALL enriched data
|
||||
try {
|
||||
const update = buildItemsBaseEnrichmentUpdate({
|
||||
@@ -874,6 +882,7 @@ export async function importSingleFurni(params: {
|
||||
canstandon,
|
||||
allowStack,
|
||||
interactionType,
|
||||
interactionModesCount,
|
||||
});
|
||||
await db.execute(update);
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { existsSync, promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db, ItemsBase } from "@/lib/db";
|
||||
import { autoDetectInteraction } from "@/lib/furni/auto-interaction";
|
||||
import { getFurniAssetWriteTargets } from "@/lib/services/furni-asset-dirs";
|
||||
import { appendFurniEntry, buildFurniEntry } from "@/lib/services/furni-data";
|
||||
import {
|
||||
@@ -158,6 +159,7 @@ async function writeSqlMigration(params: {
|
||||
customparams: string;
|
||||
price: { credits: number; points: number; pointsType: number };
|
||||
categoryKey: string;
|
||||
interactionModesCount: number;
|
||||
}): Promise<string> {
|
||||
const num = await findNextMigrationNumber();
|
||||
const padded = String(num).padStart(4, "0");
|
||||
@@ -193,12 +195,12 @@ async function writeSqlMigration(params: {
|
||||
"",
|
||||
`INSERT INTO items_base`,
|
||||
` (id, sprite_id, public_name, item_name, type, width, length, stack_height,`,
|
||||
` allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, customparams)`,
|
||||
` allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, interaction_modes_count, customparams)`,
|
||||
`VALUES`,
|
||||
` (@next_id, @next_id, ${escapeSql(params.name)}, ${escapeSql(params.classname)},`,
|
||||
` ${escapeSql(params.itemType)}, ${params.xdim}, ${params.ydim}, ${params.stackHeight},`,
|
||||
` ${params.allowStack}, ${params.cansiton ? "1" : "0"}, ${params.canlayon ? "1" : "0"}, ${params.canstandon ? "1" : "0"},`,
|
||||
` ${escapeSql(params.interactionType)}, ${escapeSql(params.customparams)});`,
|
||||
` ${escapeSql(params.interactionType)}, ${params.interactionModesCount}, ${escapeSql(params.customparams)});`,
|
||||
"",
|
||||
`INSERT INTO catalog_items`,
|
||||
` (id, page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, order_number, offer_id, extradata)`,
|
||||
@@ -328,16 +330,20 @@ export async function uploadSingleFurni(params: {
|
||||
|
||||
const price = autoPriceFurni(classname);
|
||||
|
||||
// Auto-detect interaction modes count from classname/name
|
||||
const autoInteraction = autoDetectInteraction(classname, name);
|
||||
const interactionModesCount = autoInteraction.interactionModesCount;
|
||||
|
||||
let newId: number;
|
||||
try {
|
||||
newId = await allocateItemsBaseId(async (nextId) => {
|
||||
await db.execute(sql`
|
||||
INSERT INTO items_base
|
||||
(id, sprite_id, public_name, item_name, type, width, length, stack_height,
|
||||
allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, customparams)
|
||||
allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, interaction_modes_count, customparams)
|
||||
VALUES
|
||||
(${nextId}, ${nextId}, ${name}, ${classname}, ${itemType}, ${xdim}, ${ydim}, ${stackHeight},
|
||||
${allowStack}, ${cansiton ? "1" : "0"}, ${canlayon ? "1" : "0"}, ${canstandon ? "1" : "0"}, ${userInteractionType}, ${customparams})`);
|
||||
${allowStack}, ${cansiton ? "1" : "0"}, ${canlayon ? "1" : "0"}, ${canstandon ? "1" : "0"}, ${userInteractionType}, ${interactionModesCount}, ${customparams})`);
|
||||
return nextId;
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -402,6 +408,7 @@ export async function uploadSingleFurni(params: {
|
||||
customparams,
|
||||
price,
|
||||
categoryKey: classifyFurni(classname, itemType),
|
||||
interactionModesCount,
|
||||
});
|
||||
warnings.push(`SQL migration written: ${sqlFile}`);
|
||||
} catch (err) {
|
||||
|
||||
Reference in new issue
Block a user