Merge branch 'feat/furni-names-from-json-server'

# Conflicts:
#	packages/communication/src/NitroMessages.ts
This commit is contained in:
simoleo89
2026-06-05 23:35:10 +02:00
15 changed files with 1245 additions and 3 deletions
File diff suppressed because one or more lines are too long
+2
View File
@@ -22,6 +22,7 @@ export * from './messages/incoming/crafting';
export * from './messages/incoming/desktop';
export * from './messages/incoming/friendlist';
export * from './messages/incoming/furnieditor';
export * from './messages/incoming/furniture';
export * from './messages/incoming/game';
export * from './messages/incoming/game/directory';
export * from './messages/incoming/game/lobby';
@@ -181,6 +182,7 @@ export * from './messages/parser/crafting';
export * from './messages/parser/desktop';
export * from './messages/parser/friendlist';
export * from './messages/parser/furnieditor';
export * from './messages/parser/furniture';
export * from './messages/parser/game';
export * from './messages/parser/game/directory';
export * from './messages/parser/game/lobby';
@@ -493,6 +493,7 @@ export class IncomingHeader
public static FURNI_EDITOR_DETAIL_RESULT = 10041;
public static FURNI_EDITOR_INTERACTIONS_RESULT = 10043;
public static FURNI_EDITOR_RESULT = 10044;
public static FURNITURE_DATA_RELOAD = 10047;
// Catalog Admin
public static CATALOG_ADMIN_RESULT = 10059;
@@ -0,0 +1,16 @@
import { IMessageEvent } from '@nitrots/api';
import { MessageEvent } from '@nitrots/events';
import { FurnitureDataReloadParser } from '../../parser/furniture/FurnitureDataReloadParser';
export class FurnitureDataReloadEvent extends MessageEvent implements IMessageEvent
{
constructor(callBack: Function)
{
super(callBack, FurnitureDataReloadParser);
}
public getParser(): FurnitureDataReloadParser
{
return this.parser as FurnitureDataReloadParser;
}
}
@@ -0,0 +1 @@
export * from './FurnitureDataReloadEvent';
@@ -14,6 +14,7 @@ export * from './crafting';
export * from './desktop';
export * from './friendlist';
export * from './furnieditor';
export * from './furniture';
export * from './game';
export * from './game/directory';
export * from './game/lobby';
@@ -0,0 +1,56 @@
import { IMessageDataWrapper, IMessageParser } from '@nitrots/api';
export interface FurnidataDeltaEntry
{
type: string; // "S" floor | "I" wall
id: number;
classname: string;
name: string;
description: string;
}
export class FurnitureDataReloadParser implements IMessageParser
{
private static readonly MAX_ENTRIES = 100000;
private _mode: number;
private _entries: FurnidataDeltaEntry[];
public flush(): boolean
{
this._mode = 0;
this._entries = [];
return true;
}
public parse(wrapper: IMessageDataWrapper): boolean
{
if(!wrapper) return false;
this._mode = wrapper.readInt();
this._entries = [];
if(this._mode === 0)
{
let count = wrapper.readInt();
if(count < 0) count = 0;
if(count > FurnitureDataReloadParser.MAX_ENTRIES) count = FurnitureDataReloadParser.MAX_ENTRIES;
for(let i = 0; i < count; i++)
{
this._entries.push({
type: wrapper.readString(),
id: wrapper.readInt(),
classname: wrapper.readString(),
name: wrapper.readString(),
description: wrapper.readString()
});
}
}
return true;
}
public get mode(): number { return this._mode; }
public get entries(): FurnidataDeltaEntry[] { return this._entries; }
}
@@ -0,0 +1 @@
export * from './FurnitureDataReloadParser';
@@ -13,6 +13,7 @@ export * from './crafting';
export * from './desktop';
export * from './friendlist';
export * from './furnieditor';
export * from './furniture';
export * from './game';
export * from './game/directory';
export * from './game/lobby';
+21 -2
View File
@@ -1,5 +1,7 @@
import { IFurnitureData, IGroupInformationManager, IMessageComposer, IMessageEvent, IProductData, ISessionDataManager, IUserDataSnapshot, NoobnessLevelEnum, SecurityLevel } from '@nitrots/api';
import { AccountSafetyLockStatusChangeMessageEvent, AccountSafetyLockStatusChangeParser, AvailabilityStatusMessageEvent, ChangeUserNameResultMessageEvent, EmailStatusResultEvent, FigureUpdateEvent, GetCommunication, GetUserTagsComposer, InClientLinkEvent, MysteryBoxKeysEvent, NoobnessLevelMessageEvent, PetRespectComposer, PetScratchFailedMessageEvent, RoomReadyMessageEvent, RoomUnitChatComposer, UserInfoEvent, UserNameChangeMessageEvent, UserPermissionsEvent, UserRespectComposer, UserTagsMessageEvent } from '@nitrots/communication';
import { AccountSafetyLockStatusChangeMessageEvent, AccountSafetyLockStatusChangeParser, AvailabilityStatusMessageEvent, ChangeUserNameResultMessageEvent, EmailStatusResultEvent, FigureUpdateEvent, FurnitureDataReloadEvent, GetCommunication, GetUserTagsComposer, InClientLinkEvent, MysteryBoxKeysEvent, NoobnessLevelMessageEvent, PetRespectComposer, PetScratchFailedMessageEvent, RoomReadyMessageEvent, RoomUnitChatComposer, UserInfoEvent, UserNameChangeMessageEvent, UserPermissionsEvent, UserRespectComposer, UserTagsMessageEvent } from '@nitrots/communication';
import type { FurnidataDeltaEntry } from '@nitrots/communication';
import { applyFurnidataDeltaTo } from './furniture/applyFurnidataDelta';
import { GetConfiguration } from '@nitrots/configuration';
import { GetLocalizationManager } from '@nitrots/localization';
import { GetEventDispatcher, MysteryBoxKeysUpdateEvent, NitroEvent, NitroEventType, NitroSettingsEvent, SessionDataPreferencesEvent, UserNameUpdateEvent } from '@nitrots/events';
@@ -171,7 +173,13 @@ export class SessionDataManager implements ISessionDataManager
GetCommunication().registerMessageEvent(new MysteryBoxKeysEvent(this.onMysteryBoxKeysEvent.bind(this))),
GetCommunication().registerMessageEvent(new NoobnessLevelMessageEvent(this.onNoobnessLevelMessageEvent.bind(this))),
GetCommunication().registerMessageEvent(new AccountSafetyLockStatusChangeMessageEvent(this.onAccountSafetyLockStatusChangeMessageEvent.bind(this))),
GetCommunication().registerMessageEvent(new EmailStatusResultEvent(this.onEmailStatus.bind(this)))
GetCommunication().registerMessageEvent(new EmailStatusResultEvent(this.onEmailStatus.bind(this))),
GetCommunication().registerMessageEvent(new FurnitureDataReloadEvent((event: FurnitureDataReloadEvent) =>
{
const parser = event.getParser();
if(parser.mode === 1) { void this.applyFurnidataReloadHint(); }
else { this.applyFurnidataDelta(parser.entries); }
}))
);
// Store event dispatcher callback for cleanup
@@ -564,6 +572,17 @@ export class SessionDataManager implements ISessionDataManager
}
}
public applyFurnidataDelta(entries: FurnidataDeltaEntry[]): void
{
applyFurnidataDeltaTo(entries, this._floorItems as any, this._wallItems as any, GetLocalizationManager(), (typeof window !== 'undefined') ? window : { dispatchEvent: () => {} } as any);
}
public async applyFurnidataReloadHint(): Promise<void>
{
await this._furnitureData.init();
if(typeof window !== 'undefined') window.dispatchEvent(new CustomEvent('nitro-localization-updated'));
}
public getBadgeUrl(name: string): string
{
return this._badgeImageManager.getBadgeUrl(name);
@@ -0,0 +1,36 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { applyFurnidataDeltaTo } from '../furniture/applyFurnidataDelta';
describe('applyFurnidataDeltaTo', () => {
const setValue = vi.fn();
beforeEach(() => setValue.mockClear());
it('patches floor FurnitureData name/desc + localization keys, dispatches window event', () => {
const floor: any = { _localizedName: 'Old', _description: 'Old desc' };
const floorItems = new Map<number, any>([[ 5, floor ]]);
const dispatched: string[] = [];
const win: any = { dispatchEvent: (e: any) => dispatched.push(e.type) };
applyFurnidataDeltaTo(
[ { type: 'S', id: 5, classname: 'chair', name: 'New', description: 'New desc' } ],
floorItems, new Map(), { setValue }, win
);
expect(floor._localizedName).toBe('New');
expect(floor._description).toBe('New desc');
expect(setValue).toHaveBeenCalledWith('roomItem.name.5', 'New');
expect(setValue).toHaveBeenCalledWith('roomItem.desc.5', 'New desc');
expect(dispatched).toContain('nitro-localization-updated');
});
it('patches wall items by id', () => {
const wall: any = { _localizedName: 'W', _description: '' };
const wallItems = new Map<number, any>([[ 9, wall ]]);
applyFurnidataDeltaTo(
[ { type: 'I', id: 9, classname: 'poster', name: 'WallNew', description: 'd' } ],
new Map(), wallItems, { setValue }, { dispatchEvent: () => {} }
);
expect(wall._localizedName).toBe('WallNew');
expect(setValue).toHaveBeenCalledWith('wallItem.name.9', 'WallNew');
});
});
@@ -0,0 +1,43 @@
import type { FurnidataDeltaEntry } from '@nitrots/communication';
/**
* Pure, testable furnidata-delta patcher. Mutates the FurnitureData objects in
* the given maps (by id) and the localization keys, then dispatches the
* `nitro-localization-updated` window event so subscribed React surfaces refresh.
*/
export function applyFurnidataDeltaTo(
entries: FurnidataDeltaEntry[],
floorItems: Map<number, any>,
wallItems: Map<number, any>,
localization: { setValue: (key: string, value: string) => void },
win: { dispatchEvent: (event: any) => void }
): void
{
if(!entries || !entries.length) return;
for(const e of entries)
{
if(e.type === 'I')
{
const wall = wallItems.get(e.id);
if(wall) { wall._localizedName = e.name; wall._description = e.description; }
localization.setValue('wallItem.name.' + e.id, e.name);
localization.setValue('wallItem.desc.' + e.id, e.description);
}
else
{
const floor = floorItems.get(e.id);
if(floor) { floor._localizedName = e.name; floor._description = e.description; }
localization.setValue('roomItem.name.' + e.id, e.name);
localization.setValue('roomItem.desc.' + e.id, e.description);
}
}
if(win && typeof win.dispatchEvent === 'function')
{
const evt = (typeof CustomEvent !== 'undefined')
? new CustomEvent('nitro-localization-updated')
: { type: 'nitro-localization-updated' } as any;
win.dispatchEvent(evt);
}
}