Files
Nitro_Render_V3/packages/session/src/furniture/FurnitureDataLoader.ts
T
medievalshell ae9bc8bfce feat(utils): split-aware gamedata loader with tiered merge
Introduces loadGamedata(url, options?) and mergeGamedata(a, b) in
@nitrots/utils. The loader transparently accepts:

- a single-file URL (legacy) -> parsed as before
- a directory URL ending with '/' -> tier-merged from core/custom/seasonal,
  each tier driven by its own manifest.json5

Merge rules:

- arrays of objects sharing an id key (id, classname, name): merged by id,
  later layers overriding earlier ones
- arrays without an id key: concatenated
- plain objects: recursive merge per key
- anything else: later value wins

All gamedata consumers (FurnitureDataLoader, ProductDataLoader,
EffectAssetDownloadManager, AvatarRenderManager actions+figuredata,
LocalizationManager) are migrated to loadGamedata. Behaviour is unchanged
for single-file URLs, so existing deployments need no config changes;
opt-in to split mode by appending '/' to the URL once the layout is in
place.

README updated with the directory layout, merge table and programmatic
usage example. The companion CLI splitter that produces the core/ tier
from legacy files lives in the Nitro V3 client repo.
2026-05-18 21:19:54 +02:00

137 lines
5.4 KiB
TypeScript

import { FurnitureType, IFurnitureData } from '@nitrots/api';
import { GetConfiguration } from '@nitrots/configuration';
import { GetLocalizationManager } from '@nitrots/localization';
import { loadGamedata } from '@nitrots/utils';
import { FurnitureData } from './FurnitureData';
export class FurnitureDataLoader
{
private _floorItems: Map<number, IFurnitureData>;
private _wallItems: Map<number, IFurnitureData>;
constructor(floorItems: Map<number, IFurnitureData>, wallItems: Map<number, IFurnitureData>)
{
this._floorItems = floorItems;
this._wallItems = wallItems;
}
public async init(): Promise<void>
{
const url = GetConfiguration().getValue<string>('furnidata.url');
if(!url || !url.length) throw new Error('Missing "furnidata.url" in config — add the furniture data URL to your renderer-config.json');
let responseData: any;
try
{
responseData = await loadGamedata(url);
}
catch(err)
{
throw new Error(`Could not load furniture data from "${ url }" — check "furnidata.url" in renderer-config.json (${ err?.message || err })`);
}
if(responseData.roomitemtypes) this.parseFloorItems(responseData.roomitemtypes);
if(responseData.wallitemtypes) this.parseWallItems(responseData.wallitemtypes);
}
private parseFloorItems(data: any): void
{
if(!data || !data.furnitype) return;
for(const furniture of data.furnitype)
{
if(!furniture) continue;
const colors: number[] = [];
if(furniture.partcolors)
{
for(const color of furniture.partcolors.color)
{
let colorCode = (color as string);
if(colorCode.charAt(0) === '#')
{
colorCode = colorCode.replace('#', '');
colors.push(parseInt(colorCode, 16));
}
else
{
colors.push((parseInt(colorCode, 16)));
}
}
}
const classSplit = (furniture.classname as string).split('*');
const className = classSplit[0];
const colorIndex = ((classSplit.length > 1) ? parseInt(classSplit[1]) : 0);
const hasColorIndex = (classSplit.length > 1);
const allowStack = this.resolveBooleanFlag(furniture.allowstack, furniture.allow_stack, furniture.allowStack);
const furnitureData = new FurnitureData(FurnitureType.FLOOR, furniture.id, furniture.classname, className, furniture.category, furniture.name, furniture.description, furniture.revision, furniture.xdim, furniture.ydim, 0, colors, hasColorIndex, colorIndex, furniture.adurl, furniture.offerid, furniture.buyout, furniture.rentofferid, furniture.rentbuyout, furniture.bc, furniture.customparams, furniture.specialtype, allowStack, furniture.canstandon, furniture.cansiton, furniture.canlayon, furniture.excludeddynamic, furniture.furniline, furniture.environment, furniture.rare);
this._floorItems.set(furnitureData.id, furnitureData);
this.updateLocalizations(furnitureData);
}
}
private parseWallItems(data: any): void
{
if(!data || !data.furnitype) return;
for(const furniture of data.furnitype)
{
if(!furniture) continue;
const allowStack = this.resolveBooleanFlag(furniture.allowstack, furniture.allow_stack, furniture.allowStack);
const furnitureData = new FurnitureData(FurnitureType.WALL, furniture.id, furniture.classname, furniture.classname, furniture.category, furniture.name, furniture.description, furniture.revision, 0, 0, 0, null, false, 0, furniture.adurl, furniture.offerid, furniture.buyout, furniture.rentofferid, furniture.rentbuyout, furniture.bc, null, furniture.specialtype, allowStack, false, false, false, furniture.excludeddynamic, furniture.furniline, furniture.environment, furniture.rare);
this._wallItems.set(furnitureData.id, furnitureData);
this.updateLocalizations(furnitureData);
}
}
private updateLocalizations(furniture: FurnitureData): void
{
switch(furniture.type)
{
case FurnitureType.FLOOR:
GetLocalizationManager().setValue(('roomItem.name.' + furniture.id), furniture.name);
GetLocalizationManager().setValue(('roomItem.desc.' + furniture.id), furniture.description);
return;
case FurnitureType.WALL:
GetLocalizationManager().setValue(('wallItem.name.' + furniture.id), furniture.name);
GetLocalizationManager().setValue(('wallItem.desc.' + furniture.id), furniture.description);
return;
}
}
private resolveBooleanFlag(...values: any[]): boolean
{
for(const value of values)
{
if(value === undefined || value === null) continue;
if(typeof value === 'string')
{
const normalized = value.trim().toLowerCase();
if(!normalized.length) continue;
if([ '1', 'true', 'yes' ].includes(normalized)) return true;
if([ '0', 'false', 'no' ].includes(normalized)) return false;
}
return !!value;
}
return false;
}
}