From 99c4acea38509047f75afb81322fb6c2fa050adb Mon Sep 17 00:00:00 2001 From: Lorenzune Date: Fri, 27 Mar 2026 09:37:14 +0100 Subject: [PATCH 1/5] Expose room, user and furni metadata for wired tools - parse extra room snapshot data such as hotel time, room item limit and group context - expose richer furni metadata including flags, dimensions and teleport targets - expose richer user metadata including room-entry fields and ids needed by inspection tools - keep session and room engine models aligned with the new wired monitor/inspection flow --- packages/api/src/nitro/room/IRoomCreator.ts | 6 +- packages/api/src/nitro/room/IRoomEngine.ts | 6 +- .../api/src/nitro/room/IRoomEngineServices.ts | 6 +- .../nitro/room/object/RoomObjectVariable.ts | 7 +++ .../api/src/nitro/session/IFurnitureData.ts | 1 + .../api/src/nitro/session/IRoomSession.ts | 5 ++ .../api/src/nitro/session/IRoomUserData.ts | 2 + .../GetGuestRoomResultMessageParser.ts | 28 ++++++++++ .../floor/FurnitureFloorDataParser.ts | 56 +++++++++++++++++++ .../furniture/wall/FurnitureWallDataParser.ts | 56 +++++++++++++++++++ .../parser/room/unit/RoomUnitParser.ts | 3 + .../parser/room/unit/UserMessageData.ts | 28 ++++++++++ packages/room/src/RoomEngine.ts | 26 +++++++-- packages/room/src/RoomMessageHandler.ts | 6 +- packages/room/src/utils/RoomFurnitureData.ts | 51 ++++++++++++++++- packages/session/src/RoomSession.ts | 55 ++++++++++++++++++ packages/session/src/RoomUserData.ts | 22 ++++++++ .../session/src/furniture/FurnitureData.ts | 9 ++- .../src/furniture/FurnitureDataLoader.ts | 28 +++++++++- .../session/src/handler/RoomDataHandler.ts | 5 ++ .../session/src/handler/RoomUsersHandler.ts | 2 + 21 files changed, 386 insertions(+), 22 deletions(-) diff --git a/packages/api/src/nitro/room/IRoomCreator.ts b/packages/api/src/nitro/room/IRoomCreator.ts index b4efabc..e03ac68 100644 --- a/packages/api/src/nitro/room/IRoomCreator.ts +++ b/packages/api/src/nitro/room/IRoomCreator.ts @@ -24,9 +24,9 @@ export interface IRoomCreator getRoomObjectUser(roomId: number, objectId: number): IRoomObjectController; removeRoomObjectUser(roomId: number, objectId: number): void; getRoomObjectFloor(roomId: number, objectId: number): IRoomObjectController; - addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number): boolean; - addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number): boolean; - addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, realRoomObject?: boolean): boolean; + addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; + addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; + addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, realRoomObject?: boolean, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; removeRoomObjectFloor(roomId: number, objectId: number, userId?: number, _arg_4?: boolean): void; removeRoomObjectWall(roomId: number, objectId: number, userId?: number): void; updateRoomObjectFloor(roomId: number, objectId: number, location: IVector3D, direction: IVector3D, state: number, data: IObjectData, extra?: number): boolean; diff --git a/packages/api/src/nitro/room/IRoomEngine.ts b/packages/api/src/nitro/room/IRoomEngine.ts index 01916c9..78fcc4b 100644 --- a/packages/api/src/nitro/room/IRoomEngine.ts +++ b/packages/api/src/nitro/room/IRoomEngine.ts @@ -46,9 +46,9 @@ export interface IRoomEngine updateRoomObjectWallLocation(roomId: number, objectId: number, location: IVector3D): boolean; addRoomObjectUser(roomId: number, objectId: number, location: IVector3D, direction: IVector3D, headDirection: number, type: number, figure: string): boolean; updateRoomObjectUserLocation(roomId: number, objectId: number, location: IVector3D, targetLocation: IVector3D, canStandUp?: boolean, baseY?: number, direction?: IVector3D, headDirection?: number, skipLocationFix?: boolean, isSlide?: boolean, duration?: number): boolean; - addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number): boolean; - addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number): boolean; - addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, realRoomObject?: boolean): boolean; + addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; + addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; + addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, realRoomObject?: boolean, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; initalizeTemporaryObjectsByType(type: string, _arg_2: boolean): void; updateRoomObjectFloor(roomId: number, objectId: number, location: IVector3D, direction: IVector3D, state: number, data: IObjectData, extra?: number): boolean; updateRoomObjectWall(roomId: number, objectId: number, location: IVector3D, direction: IVector3D, state: number, extra?: string): boolean; diff --git a/packages/api/src/nitro/room/IRoomEngineServices.ts b/packages/api/src/nitro/room/IRoomEngineServices.ts index 40df0ac..4acb50c 100644 --- a/packages/api/src/nitro/room/IRoomEngineServices.ts +++ b/packages/api/src/nitro/room/IRoomEngineServices.ts @@ -22,9 +22,9 @@ export interface IRoomEngineServices getRoomObjectCursor(roomId: number): IRoomObjectController; getRoomObjectSelectionArrow(roomId: number): IRoomObjectController; addRoomObjectUser(roomId: number, objectId: number, location: IVector3D, direction: IVector3D, headDirection: number, type: number, figure: string): boolean; - addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number): boolean; - addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number): boolean; - addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, realRoomObject?: boolean): boolean; + addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; + addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra?: number, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, synchronized?: boolean, realRoomObject?: boolean, sizeZ?: number, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; + addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires?: number, usagePolicy?: number, ownerId?: number, ownerName?: string, realRoomObject?: boolean, allowStack?: boolean, allowSit?: boolean, allowLay?: boolean, allowWalk?: boolean, dimensionsX?: number, dimensionsY?: number, teleportTargetId?: number): boolean; removeRoomObjectFloor(roomId: number, objectId: number, userId?: number, _arg_4?: boolean): void; removeRoomObjectWall(roomId: number, objectId: number, userId?: number): void; removeRoomObjectUser(roomId: number, objectId: number): void; diff --git a/packages/api/src/nitro/room/object/RoomObjectVariable.ts b/packages/api/src/nitro/room/object/RoomObjectVariable.ts index 229de7a..8b8950d 100644 --- a/packages/api/src/nitro/room/object/RoomObjectVariable.ts +++ b/packages/api/src/nitro/room/object/RoomObjectVariable.ts @@ -83,6 +83,13 @@ export class RoomObjectVariable public static FURNITURE_USAGE_POLICY: string = 'furniture_usage_policy'; public static FURNITURE_OWNER_ID: string = 'furniture_owner_id'; public static FURNITURE_OWNER_NAME: string = 'furniture_owner_name'; + public static FURNITURE_ALLOW_STACK: string = 'furniture_allow_stack'; + public static FURNITURE_ALLOW_SIT: string = 'furniture_allow_sit'; + public static FURNITURE_ALLOW_LAY: string = 'furniture_allow_lay'; + public static FURNITURE_ALLOW_WALK: string = 'furniture_allow_walk'; + public static FURNITURE_DIMENSIONS_X: string = 'furniture_dimensions_x'; + public static FURNITURE_DIMENSIONS_Y: string = 'furniture_dimensions_y'; + public static FURNITURE_TELEPORT_TARGET_ID: string = 'furniture_teleport_target_id'; public static FURNITURE_ROOM_BACKGROUND_COLOR_HUE: string = 'furniture_room_background_color_hue'; public static FURNITURE_ROOM_BACKGROUND_COLOR_SATURATION: string = 'furniture_room_background_color_saturation'; public static FURNITURE_ROOM_BACKGROUND_COLOR_LIGHTNESS: string = 'furniture_room_background_color_lightness'; diff --git a/packages/api/src/nitro/session/IFurnitureData.ts b/packages/api/src/nitro/session/IFurnitureData.ts index e70dae0..ac77f64 100644 --- a/packages/api/src/nitro/session/IFurnitureData.ts +++ b/packages/api/src/nitro/session/IFurnitureData.ts @@ -24,6 +24,7 @@ export interface IFurnitureData purchaseCouldBeUsedForBuyout: boolean; rentCouldBeUsedForBuyout: boolean; availableForBuildersClub: boolean; + allowStack: boolean; canStandOn: boolean; canSitOn: boolean; canLayOn: boolean; diff --git a/packages/api/src/nitro/session/IRoomSession.ts b/packages/api/src/nitro/session/IRoomSession.ts index 097623f..02a905e 100644 --- a/packages/api/src/nitro/session/IRoomSession.ts +++ b/packages/api/src/nitro/session/IRoomSession.ts @@ -57,6 +57,11 @@ export interface IRoomSession allowPets: boolean; controllerLevel: number; ownRoomIndex: number; + groupId: number; + hotelTimeZone: string; + hotelTimeSnapshotMs: number; + hotelTimeSyncMs: number; + roomItemLimit: number; isGuildRoom: boolean; isRoomOwner: boolean; isDecorating: boolean; diff --git a/packages/api/src/nitro/session/IRoomUserData.ts b/packages/api/src/nitro/session/IRoomUserData.ts index 09eaa57..4407449 100644 --- a/packages/api/src/nitro/session/IRoomUserData.ts +++ b/packages/api/src/nitro/session/IRoomUserData.ts @@ -23,4 +23,6 @@ export interface IRoomUserData petLevel: number; botSkills: number[]; isModerator: boolean; + roomEntryMethod: string; + roomEntryTeleportId: number; } diff --git a/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts b/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts index ebebe41..0595de2 100644 --- a/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts +++ b/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts @@ -11,6 +11,9 @@ export class GetGuestRoomResultMessageParser implements IMessageParser private _isGroupMember: boolean; private _moderation: RoomModerationSettings; private _chat: RoomChatSettings; + private _hotelTimeZoneId: string; + private _hotelCurrentTimeMs: number; + private _roomItemLimit: number; public flush(): boolean { @@ -21,6 +24,9 @@ export class GetGuestRoomResultMessageParser implements IMessageParser this._isGroupMember = false; this._moderation = null; this._chat = null; + this._hotelTimeZoneId = null; + this._hotelCurrentTimeMs = 0; + this._roomItemLimit = 0; return true; } @@ -39,6 +45,13 @@ export class GetGuestRoomResultMessageParser implements IMessageParser this.data.canMute = wrapper.readBoolean(); this._chat = new RoomChatSettings(wrapper); + if(wrapper.bytesAvailable) + { + this._hotelTimeZoneId = wrapper.readString(); + this._hotelCurrentTimeMs = Number(wrapper.readString()) || 0; + if(wrapper.bytesAvailable) this._roomItemLimit = wrapper.readInt(); + } + return true; } @@ -76,4 +89,19 @@ export class GetGuestRoomResultMessageParser implements IMessageParser { return this._chat; } + + public get hotelTimeZoneId(): string + { + return this._hotelTimeZoneId; + } + + public get hotelCurrentTimeMs(): number + { + return this._hotelCurrentTimeMs; + } + + public get roomItemLimit(): number + { + return this._roomItemLimit; + } } diff --git a/packages/communication/src/messages/parser/room/furniture/floor/FurnitureFloorDataParser.ts b/packages/communication/src/messages/parser/room/furniture/floor/FurnitureFloorDataParser.ts index fb4ac09..ffff026 100644 --- a/packages/communication/src/messages/parser/room/furniture/floor/FurnitureFloorDataParser.ts +++ b/packages/communication/src/messages/parser/room/furniture/floor/FurnitureFloorDataParser.ts @@ -25,6 +25,13 @@ export class FurnitureFloorDataParser private _usagePolicy: number; private _userId: number; private _username: string; + private _allowStack: boolean; + private _allowSit: boolean; + private _allowLay: boolean; + private _allowWalk: boolean; + private _dimensionsX: number; + private _dimensionsY: number; + private _teleportTargetId: number; constructor(wrapper: IMessageDataWrapper) { @@ -51,6 +58,13 @@ export class FurnitureFloorDataParser this._usagePolicy = 0; this._userId = 0; this._username = null; + this._allowStack = false; + this._allowSit = false; + this._allowLay = false; + this._allowWalk = false; + this._dimensionsX = 0; + this._dimensionsY = 0; + this._teleportTargetId = 0; return true; } @@ -72,6 +86,13 @@ export class FurnitureFloorDataParser this._expires = wrapper.readInt(); this._usagePolicy = wrapper.readInt(); this._userId = wrapper.readInt(); + this._allowStack = (wrapper.readInt() === 1); + this._allowSit = (wrapper.readInt() === 1); + this._allowLay = (wrapper.readInt() === 1); + this._allowWalk = (wrapper.readInt() === 1); + this._dimensionsX = wrapper.readInt(); + this._dimensionsY = wrapper.readInt(); + this._teleportTargetId = wrapper.readInt(); this._username = null; if(this._spriteId < 0) this._spriteName = wrapper.readString(); @@ -149,6 +170,41 @@ export class FurnitureFloorDataParser return this._username; } + public get allowStack(): boolean + { + return this._allowStack; + } + + public get allowSit(): boolean + { + return this._allowSit; + } + + public get allowLay(): boolean + { + return this._allowLay; + } + + public get allowWalk(): boolean + { + return this._allowWalk; + } + + public get dimensionsX(): number + { + return this._dimensionsX; + } + + public get dimensionsY(): number + { + return this._dimensionsY; + } + + public get teleportTargetId(): number + { + return this._teleportTargetId; + } + public set username(username: string) { this._username = username; diff --git a/packages/communication/src/messages/parser/room/furniture/wall/FurnitureWallDataParser.ts b/packages/communication/src/messages/parser/room/furniture/wall/FurnitureWallDataParser.ts index a716e74..db7b5fb 100644 --- a/packages/communication/src/messages/parser/room/furniture/wall/FurnitureWallDataParser.ts +++ b/packages/communication/src/messages/parser/room/furniture/wall/FurnitureWallDataParser.ts @@ -11,6 +11,13 @@ export class FurnitureWallDataParser private _usagePolicy: number; private _userId: number; private _username: string; + private _allowStack: boolean; + private _allowSit: boolean; + private _allowLay: boolean; + private _allowWalk: boolean; + private _dimensionsX: number; + private _dimensionsY: number; + private _teleportTargetId: number; private _width: number; private _height: number; @@ -40,6 +47,13 @@ export class FurnitureWallDataParser this._usagePolicy = -1; this._userId = 0; this._username = null; + this._allowStack = false; + this._allowSit = false; + this._allowLay = false; + this._allowWalk = false; + this._dimensionsX = 0; + this._dimensionsY = 0; + this._teleportTargetId = 0; this._width = 0; this._height = 0; @@ -64,6 +78,13 @@ export class FurnitureWallDataParser this._secondsToExpiration = wrapper.readInt(); this._usagePolicy = wrapper.readInt(); this._userId = wrapper.readInt(); + this._allowStack = (wrapper.readInt() === 1); + this._allowSit = (wrapper.readInt() === 1); + this._allowLay = (wrapper.readInt() === 1); + this._allowWalk = (wrapper.readInt() === 1); + this._dimensionsX = wrapper.readInt(); + this._dimensionsY = wrapper.readInt(); + this._teleportTargetId = wrapper.readInt(); this._username = null; const state = parseFloat(this._stuffData); @@ -191,6 +212,41 @@ export class FurnitureWallDataParser return this._username; } + public get allowStack(): boolean + { + return this._allowStack; + } + + public get allowSit(): boolean + { + return this._allowSit; + } + + public get allowLay(): boolean + { + return this._allowLay; + } + + public get allowWalk(): boolean + { + return this._allowWalk; + } + + public get dimensionsX(): number + { + return this._dimensionsX; + } + + public get dimensionsY(): number + { + return this._dimensionsY; + } + + public get teleportTargetId(): number + { + return this._teleportTargetId; + } + public set username(username: string) { this._username = username; diff --git a/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts b/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts index eb2f96d..21a50a5 100644 --- a/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts +++ b/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts @@ -135,6 +135,9 @@ export class RoomUnitParser implements IMessageParser } } + user.roomEntryMethod = wrapper.readString(); + user.roomEntryTeleportId = wrapper.readInt(); + i++; } diff --git a/packages/communication/src/messages/parser/room/unit/UserMessageData.ts b/packages/communication/src/messages/parser/room/unit/UserMessageData.ts index ef4c5d9..e4e2270 100644 --- a/packages/communication/src/messages/parser/room/unit/UserMessageData.ts +++ b/packages/communication/src/messages/parser/room/unit/UserMessageData.ts @@ -35,6 +35,8 @@ export class UserMessageData private _petPosture: string = ''; private _botSkills: number[] = []; private _isModerator: boolean = false; + private _roomEntryMethod: string = 'unknown'; + private _roomEntryTeleportId: number = 0; private _isReadOnly: boolean = false; constructor(k: number) @@ -442,4 +444,30 @@ export class UserMessageData this._isModerator = k; } } + + public get roomEntryMethod(): string + { + return this._roomEntryMethod; + } + + public set roomEntryMethod(k: string) + { + if(!this._isReadOnly) + { + this._roomEntryMethod = k; + } + } + + public get roomEntryTeleportId(): number + { + return this._roomEntryTeleportId; + } + + public set roomEntryTeleportId(k: number) + { + if(!this._isReadOnly) + { + this._roomEntryTeleportId = k; + } + } } diff --git a/packages/room/src/RoomEngine.ts b/packages/room/src/RoomEngine.ts index 587d351..ae6781c 100644 --- a/packages/room/src/RoomEngine.ts +++ b/packages/room/src/RoomEngine.ts @@ -819,6 +819,13 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService model.setValue(RoomObjectVariable.FURNITURE_USAGE_POLICY, data.usagePolicy); model.setValue(RoomObjectVariable.FURNITURE_OWNER_ID, data.ownerId); model.setValue(RoomObjectVariable.FURNITURE_OWNER_NAME, data.ownerName); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_STACK, data.allowStack ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_SIT, data.allowSit ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_LAY, data.allowLay ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_WALK, data.allowWalk ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_DIMENSIONS_X, data.dimensionsX); + model.setValue(RoomObjectVariable.FURNITURE_DIMENSIONS_Y, data.dimensionsY); + model.setValue(RoomObjectVariable.FURNITURE_TELEPORT_TARGET_ID, data.teleportTargetId); } if(!this.updateRoomObjectFloor(roomId, id, data.location, data.direction, data.state, data.data, data.extra)) return false; @@ -879,6 +886,13 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService model.setValue(RoomObjectVariable.FURNITURE_USAGE_POLICY, data.usagePolicy); model.setValue(RoomObjectVariable.FURNITURE_OWNER_ID, data.ownerId); model.setValue(RoomObjectVariable.FURNITURE_OWNER_NAME, data.ownerName); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_STACK, data.allowStack ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_SIT, data.allowSit ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_LAY, data.allowLay ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_ALLOW_WALK, data.allowWalk ? 1 : 0); + model.setValue(RoomObjectVariable.FURNITURE_DIMENSIONS_X, data.dimensionsX); + model.setValue(RoomObjectVariable.FURNITURE_DIMENSIONS_Y, data.dimensionsY); + model.setValue(RoomObjectVariable.FURNITURE_TELEPORT_TARGET_ID, data.teleportTargetId); } if(!this.updateRoomObjectWall(roomId, id, data.location, data.direction, data.state, extra)) return false; @@ -1667,33 +1681,33 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService if(GetEventDispatcher()) GetEventDispatcher().dispatchEvent(new RoomEngineObjectEvent(RoomEngineObjectEvent.REMOVED, roomId, objectId, category)); } - public addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra: number = NaN, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', synchronized: boolean = true, realRoomObject: boolean = true, sizeZ: number = -1): boolean + public addFurnitureFloor(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra: number = NaN, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', synchronized: boolean = true, realRoomObject: boolean = true, sizeZ: number = -1, allowStack: boolean = false, allowSit: boolean = false, allowLay: boolean = false, allowWalk: boolean = false, dimensionsX: number = 0, dimensionsY: number = 0, teleportTargetId: number = 0): boolean { const instanceData = this.getRoomInstanceData(roomId); if(!instanceData) return false; - const furnitureData = new RoomFurnitureData(id, typeId, null, location, direction, state, objectData, extra, expires, usagePolicy, ownerId, ownerName, synchronized, realRoomObject, sizeZ); + const furnitureData = new RoomFurnitureData(id, typeId, null, location, direction, state, objectData, extra, expires, usagePolicy, ownerId, ownerName, synchronized, realRoomObject, sizeZ, allowStack, allowSit, allowLay, allowWalk, dimensionsX, dimensionsY, teleportTargetId); instanceData.addPendingFurnitureFloor(furnitureData); return true; } - public addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra: number = NaN, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', synchronized: boolean = true, realRoomObject: boolean = true, sizeZ: number = -1): boolean + public addFurnitureFloorByTypeName(roomId: number, id: number, typeName: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra: number = NaN, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', synchronized: boolean = true, realRoomObject: boolean = true, sizeZ: number = -1, allowStack: boolean = false, allowSit: boolean = false, allowLay: boolean = false, allowWalk: boolean = false, dimensionsX: number = 0, dimensionsY: number = 0, teleportTargetId: number = 0): boolean { const instanceData = this.getRoomInstanceData(roomId); if(!instanceData) return false; - const furnitureData = new RoomFurnitureData(id, 0, typeName, location, direction, state, objectData, extra, expires, usagePolicy, ownerId, ownerName, synchronized, realRoomObject, sizeZ); + const furnitureData = new RoomFurnitureData(id, 0, typeName, location, direction, state, objectData, extra, expires, usagePolicy, ownerId, ownerName, synchronized, realRoomObject, sizeZ, allowStack, allowSit, allowLay, allowWalk, dimensionsX, dimensionsY, teleportTargetId); instanceData.addPendingFurnitureFloor(furnitureData); return true; } - public addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', realRoomObject: boolean = true): boolean + public addFurnitureWall(roomId: number, id: number, typeId: number, location: IVector3D, direction: IVector3D, state: number, extra: string, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', realRoomObject: boolean = true, allowStack: boolean = false, allowSit: boolean = false, allowLay: boolean = false, allowWalk: boolean = false, dimensionsX: number = 0, dimensionsY: number = 0, teleportTargetId: number = 0): boolean { const instanceData = this.getRoomInstanceData(roomId); @@ -1703,7 +1717,7 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService objectData.setString(extra); - const furnitureData = new RoomFurnitureData(id, typeId, null, location, direction, state, objectData, NaN, expires, usagePolicy, ownerId, ownerName, true, realRoomObject); + const furnitureData = new RoomFurnitureData(id, typeId, null, location, direction, state, objectData, NaN, expires, usagePolicy, ownerId, ownerName, true, realRoomObject, -1, allowStack, allowSit, allowLay, allowWalk, dimensionsX, dimensionsY, teleportTargetId); instanceData.addPendingFurnitureWall(furnitureData); diff --git a/packages/room/src/RoomMessageHandler.ts b/packages/room/src/RoomMessageHandler.ts index a6eefa9..706b3d8 100644 --- a/packages/room/src/RoomMessageHandler.ts +++ b/packages/room/src/RoomMessageHandler.ts @@ -1167,11 +1167,11 @@ export class RoomMessageHandler if(data.spriteName) { - this._roomEngine.addFurnitureFloorByTypeName(roomId, data.itemId, data.spriteName, location, direction, data.state, data.data, data.extra, data.expires, data.usagePolicy, data.userId, data.username, true, true, data.stackHeight); + this._roomEngine.addFurnitureFloorByTypeName(roomId, data.itemId, data.spriteName, location, direction, data.state, data.data, data.extra, data.expires, data.usagePolicy, data.userId, data.username, true, true, data.stackHeight, data.allowStack, data.allowSit, data.allowLay, data.allowWalk, data.dimensionsX, data.dimensionsY, data.teleportTargetId); } else { - this._roomEngine.addFurnitureFloor(roomId, data.itemId, data.spriteId, location, direction, data.state, data.data, data.extra, data.expires, data.usagePolicy, data.userId, data.username, true, true, data.stackHeight); + this._roomEngine.addFurnitureFloor(roomId, data.itemId, data.spriteId, location, direction, data.state, data.data, data.extra, data.expires, data.usagePolicy, data.userId, data.username, true, true, data.stackHeight, data.allowStack, data.allowSit, data.allowLay, data.allowWalk, data.dimensionsX, data.dimensionsY, data.teleportTargetId); } } @@ -1196,7 +1196,7 @@ export class RoomMessageHandler const direction = new Vector3d(wallGeometry.getDirection(data.direction)); - this._roomEngine.addFurnitureWall(roomId, data.itemId, data.spriteId, location, direction, data.state, data.stuffData, data.secondsToExpiration, data.usagePolicy, data.userId, data.username); + this._roomEngine.addFurnitureWall(roomId, data.itemId, data.spriteId, location, direction, data.state, data.stuffData, data.secondsToExpiration, data.usagePolicy, data.userId, data.username, true, data.allowStack, data.allowSit, data.allowLay, data.allowWalk, data.dimensionsX, data.dimensionsY, data.teleportTargetId); } private onIgnoreResultEvent(event: IgnoreResultEvent): void diff --git a/packages/room/src/utils/RoomFurnitureData.ts b/packages/room/src/utils/RoomFurnitureData.ts index 3475406..356d00b 100644 --- a/packages/room/src/utils/RoomFurnitureData.ts +++ b/packages/room/src/utils/RoomFurnitureData.ts @@ -18,8 +18,15 @@ export class RoomFurnitureData private _synchronized: boolean; private _realRoomObject: boolean; private _sizeZ: number; + private _allowStack: boolean; + private _allowSit: boolean; + private _allowLay: boolean; + private _allowWalk: boolean; + private _dimensionsX: number; + private _dimensionsY: number; + private _teleportTargetId: number; - constructor(id: number, typeId: number, type: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra: number = NaN, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', synchronized: boolean = true, realRoomObject: boolean = true, sizeZ: number = -1) + constructor(id: number, typeId: number, type: string, location: IVector3D, direction: IVector3D, state: number, objectData: IObjectData, extra: number = NaN, expires: number = -1, usagePolicy: number = 0, ownerId: number = 0, ownerName: string = '', synchronized: boolean = true, realRoomObject: boolean = true, sizeZ: number = -1, allowStack: boolean = false, allowSit: boolean = false, allowLay: boolean = false, allowWalk: boolean = false, dimensionsX: number = 0, dimensionsY: number = 0, teleportTargetId: number = 0) { this._id = id; this._typeId = typeId; @@ -34,6 +41,13 @@ export class RoomFurnitureData this._synchronized = synchronized; this._realRoomObject = realRoomObject; this._sizeZ = sizeZ; + this._allowStack = allowStack; + this._allowSit = allowSit; + this._allowLay = allowLay; + this._allowWalk = allowWalk; + this._dimensionsX = dimensionsX; + this._dimensionsY = dimensionsY; + this._teleportTargetId = teleportTargetId; this._location = new Vector3d(); this._direction = new Vector3d(); @@ -116,4 +130,39 @@ export class RoomFurnitureData { return this._sizeZ; } + + public get allowStack(): boolean + { + return this._allowStack; + } + + public get allowSit(): boolean + { + return this._allowSit; + } + + public get allowLay(): boolean + { + return this._allowLay; + } + + public get allowWalk(): boolean + { + return this._allowWalk; + } + + public get dimensionsX(): number + { + return this._dimensionsX; + } + + public get dimensionsY(): number + { + return this._dimensionsY; + } + + public get teleportTargetId(): number + { + return this._teleportTargetId; + } } diff --git a/packages/session/src/RoomSession.ts b/packages/session/src/RoomSession.ts index 500cc57..853bd41 100644 --- a/packages/session/src/RoomSession.ts +++ b/packages/session/src/RoomSession.ts @@ -17,6 +17,11 @@ export class RoomSession implements IRoomSession private _allowPets: boolean = false; private _controllerLevel: number = RoomControllerLevel.NONE; private _ownRoomIndex: number = -1; + private _groupId: number = 0; + private _hotelTimeZone: string = 'UTC'; + private _hotelTimeSnapshotMs: number = 0; + private _hotelTimeSyncMs: number = 0; + private _roomItemLimit: number = 0; private _isGuildRoom: boolean = false; private _isRoomOwner: boolean = false; private _isDecorating: boolean = false; @@ -398,6 +403,56 @@ export class RoomSession implements IRoomSession return this._ownRoomIndex; } + public get groupId(): number + { + return this._groupId; + } + + public set groupId(groupId: number) + { + this._groupId = groupId; + } + + public get hotelTimeZone(): string + { + return this._hotelTimeZone; + } + + public set hotelTimeZone(timeZone: string) + { + this._hotelTimeZone = (timeZone || 'UTC'); + } + + public get hotelTimeSnapshotMs(): number + { + return this._hotelTimeSnapshotMs; + } + + public set hotelTimeSnapshotMs(value: number) + { + this._hotelTimeSnapshotMs = value; + } + + public get hotelTimeSyncMs(): number + { + return this._hotelTimeSyncMs; + } + + public set hotelTimeSyncMs(value: number) + { + this._hotelTimeSyncMs = value; + } + + public get roomItemLimit(): number + { + return this._roomItemLimit; + } + + public set roomItemLimit(value: number) + { + this._roomItemLimit = value; + } + public get isGuildRoom(): boolean { return this._isGuildRoom; diff --git a/packages/session/src/RoomUserData.ts b/packages/session/src/RoomUserData.ts index 02e8d67..201cca3 100644 --- a/packages/session/src/RoomUserData.ts +++ b/packages/session/src/RoomUserData.ts @@ -28,6 +28,8 @@ export class RoomUserData implements IRoomUserData private _hasBreedingPermission: boolean; private _botSkills: number[]; private _isModerator: boolean; + private _roomEntryMethod: string = 'unknown'; + private _roomEntryTeleportId: number = 0; constructor(k: number) { @@ -288,4 +290,24 @@ export class RoomUserData implements IRoomUserData { this._isModerator = k; } + + public get roomEntryMethod(): string + { + return this._roomEntryMethod; + } + + public set roomEntryMethod(k: string) + { + this._roomEntryMethod = k; + } + + public get roomEntryTeleportId(): number + { + return this._roomEntryTeleportId; + } + + public set roomEntryTeleportId(k: number) + { + this._roomEntryTeleportId = k; + } } diff --git a/packages/session/src/furniture/FurnitureData.ts b/packages/session/src/furniture/FurnitureData.ts index b27e29e..ed2dffd 100644 --- a/packages/session/src/furniture/FurnitureData.ts +++ b/packages/session/src/furniture/FurnitureData.ts @@ -24,6 +24,7 @@ export class FurnitureData implements IFurnitureData private _purchaseCouldBeUsedForBuyout: boolean; private _rentCouldBeUsedForBuyout: boolean; private _availableForBuildersClub: boolean; + private _allowStack: boolean; private _canStandOn: boolean; private _canSitOn: boolean; private _canLayOn: boolean; @@ -32,7 +33,7 @@ export class FurnitureData implements IFurnitureData private _environment: string; private _rare: boolean; - constructor(type: FurnitureType, id: number, fullName: string, className: string, category: string, localizedName: string, description: string, revision: number, tileSizeX: number, tileSizeY: number, tileSizeZ: number, colors: number[], hadIndexedColor: boolean, colorIndex: number, adUrl: string, purchaseOfferId: number, purchaseCouldBeUsedForBuyout: boolean, rentOfferId: number, rentCouldBeUsedForBuyout: boolean, availableForBuildersClub: boolean, customParams: string, specialType: number, canStandOn: boolean, canSitOn: boolean, canLayOn: boolean, excludedfromDynamic: boolean, furniLine: string, environment: string, rare: boolean) + constructor(type: FurnitureType, id: number, fullName: string, className: string, category: string, localizedName: string, description: string, revision: number, tileSizeX: number, tileSizeY: number, tileSizeZ: number, colors: number[], hadIndexedColor: boolean, colorIndex: number, adUrl: string, purchaseOfferId: number, purchaseCouldBeUsedForBuyout: boolean, rentOfferId: number, rentCouldBeUsedForBuyout: boolean, availableForBuildersClub: boolean, customParams: string, specialType: number, allowStack: boolean, canStandOn: boolean, canSitOn: boolean, canLayOn: boolean, excludedfromDynamic: boolean, furniLine: string, environment: string, rare: boolean) { this._type = type; this._id = id; @@ -56,6 +57,7 @@ export class FurnitureData implements IFurnitureData this._customParams = customParams; this._specialType = specialType; this._availableForBuildersClub = availableForBuildersClub; + this._allowStack = allowStack; this._canStandOn = canStandOn; this._canSitOn = canSitOn; this._canLayOn = canLayOn; @@ -180,6 +182,11 @@ export class FurnitureData implements IFurnitureData return this._availableForBuildersClub; } + public get allowStack(): boolean + { + return this._allowStack; + } + public get canStandOn(): boolean { return this._canStandOn; diff --git a/packages/session/src/furniture/FurnitureDataLoader.ts b/packages/session/src/furniture/FurnitureDataLoader.ts index 1d407c3..9505fe0 100644 --- a/packages/session/src/furniture/FurnitureDataLoader.ts +++ b/packages/session/src/furniture/FurnitureDataLoader.ts @@ -82,8 +82,9 @@ export class FurnitureDataLoader 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, furniture.canstandon, furniture.cansiton, furniture.canlayon, furniture.excludeddynamic, furniture.furniline, furniture.environment, furniture.rare); + 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); @@ -99,7 +100,8 @@ export class FurnitureDataLoader { if(!furniture) continue; - 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, false, false, false, furniture.excludeddynamic, furniture.furniline, furniture.environment, furniture.rare); + 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); @@ -121,4 +123,26 @@ export class FurnitureDataLoader 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; + } } diff --git a/packages/session/src/handler/RoomDataHandler.ts b/packages/session/src/handler/RoomDataHandler.ts index 67aa7d3..0cd169e 100644 --- a/packages/session/src/handler/RoomDataHandler.ts +++ b/packages/session/src/handler/RoomDataHandler.ts @@ -29,6 +29,11 @@ export class RoomDataHandler extends BaseHandler const roomData = parser.data; roomSession.tradeMode = roomData.tradeMode; + roomSession.groupId = roomData.habboGroupId; + roomSession.hotelTimeZone = (parser.hotelTimeZoneId || 'UTC'); + roomSession.hotelTimeSnapshotMs = parser.hotelCurrentTimeMs; + roomSession.hotelTimeSyncMs = Date.now(); + roomSession.roomItemLimit = parser.roomItemLimit; roomSession.isGuildRoom = (roomData.habboGroupId !== 0); roomSession.doorMode = roomData.doorMode; roomSession.allowPets = roomData.allowPets; diff --git a/packages/session/src/handler/RoomUsersHandler.ts b/packages/session/src/handler/RoomUsersHandler.ts index 9e5648d..9d04153 100644 --- a/packages/session/src/handler/RoomUsersHandler.ts +++ b/packages/session/src/handler/RoomUsersHandler.ts @@ -78,6 +78,8 @@ export class RoomUsersHandler extends BaseHandler userData.petLevel = user.petLevel; userData.botSkills = user.botSkills; userData.isModerator = user.isModerator; + userData.roomEntryMethod = user.roomEntryMethod; + userData.roomEntryTeleportId = user.roomEntryTeleportId; if(!session.userDataManager.getUserData(user.roomIndex)) usersToAdd.push(userData); From 8f1436a81c0274a88b15a51143ec312b4712797c Mon Sep 17 00:00:00 2001 From: duckietm Date: Tue, 31 Mar 2026 11:41:42 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=86=99=20Fix=20Youtube=20TV's?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FurnitureDynamicThumbnailVisualization.ts | 8 +- .../IsometricImageFurniVisualization.ts | 91 +++++++++++-------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts index 1fdb4d2..47f3959 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts @@ -28,11 +28,15 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV if (image.complete && image.width > 0 && image.height > 0) { const texture = Texture.from(image); texture.source.scaleMode = 'linear'; - this.setThumbnailImages(texture, thumbnailUrl); // Pass URL here + this.setThumbnailImages(texture, thumbnailUrl); } else { - console.error("Image failed to load properly:", thumbnailUrl); + this.setThumbnailImages(null); } }; + + image.onerror = () => { + this.setThumbnailImages(null); + }; } else { this.setThumbnailImages(null); } diff --git a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts index 3e5ad89..f3f79ce 100644 --- a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts +++ b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts @@ -56,13 +56,10 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza return; } - const thumbnailAssetName = this.getThumbnailAssetName(64); - if (this._thumbnailImageNormal) { this.addThumbnailAsset(this._thumbnailImageNormal, 64); } else { - const layerId = 2; - const sprite = this.getSprite(layerId); + this.asset.disposeAsset(`${this.getThumbnailAssetName(64)}-${this._uniqueId}`); } this._thumbnailChanged = false; @@ -101,60 +98,78 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza } protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture { - const scaleFactor = (asset?.width || 64) / texture.width; - const verticalScale = 1.0265; - const matrix = new Matrix(); - const frameThickness = 20; - const frameColor = 0x000000; + const assetWidth = asset?.width || 64; + const assetHeight = asset?.height || 64; - switch (this.direction) { + if(this._hasOutline) + { + const borderSize = 20; + const bgWidth = texture.width + borderSize * 2; + const bgHeight = texture.height + borderSize * 2; + + const container = new Container(); + const background = new Sprite(Texture.WHITE); + background.tint = 0x000000; + background.width = bgWidth; + background.height = bgHeight; + + const imageSprite = new Sprite(texture); + imageSprite.position.set(borderSize, borderSize); + + container.addChild(background, imageSprite); + + const flatRenderTexture = RenderTexture.create({ width: bgWidth, height: bgHeight, resolution: 1 }); + GetRenderer().render({ container, target: flatRenderTexture, clear: true }); + + texture = flatRenderTexture; + } + + texture.source.scaleMode = 'linear'; + + const scaleX = assetWidth / texture.width; + const scaleY = assetHeight / texture.height; + + const matrix = new Matrix(); + + switch(this.direction) + { case 2: - matrix.a = scaleFactor; - matrix.b = (-0.5 * scaleFactor); + matrix.a = scaleX; + matrix.b = -(0.5 * scaleX); matrix.c = 0; - matrix.d = (scaleFactor * verticalScale); + matrix.d = (scaleY / 1.6); matrix.tx = 0; - matrix.ty = (0.5 * scaleFactor * texture.width); + matrix.ty = (0.5 * scaleX * texture.width); break; case 0: case 4: - matrix.a = scaleFactor; - matrix.b = (0.5 * scaleFactor); + matrix.a = scaleX; + matrix.b = (0.5 * scaleX); matrix.c = 0; - matrix.d = (scaleFactor * verticalScale); + matrix.d = (scaleY / 1.6); matrix.tx = 0; matrix.ty = 0; break; default: - matrix.a = scaleFactor; + matrix.a = scaleX; matrix.b = 0; matrix.c = 0; - matrix.d = scaleFactor; + matrix.d = scaleY; matrix.tx = 0; matrix.ty = 0; } - const imgWidth = texture.width; - const imgHeight = texture.height; - const flatWidth = imgWidth + frameThickness * 2; - const flatHeight = imgHeight + frameThickness * 2; - const flatRenderTexture = TextureUtils.createAndFillRenderTexture(flatWidth, flatHeight, frameColor); - const imageSprite = new Sprite(texture); - imageSprite.position.set(frameThickness, frameThickness); - TextureUtils.writeToTexture(imageSprite, flatRenderTexture, false); - const flatTexture = flatRenderTexture; - const transformedSprite = new Sprite(flatTexture); + const transformedSprite = new Sprite(texture); transformedSprite.setFromMatrix(matrix); - const width = 80; - const height = 80; - const finalContainer = new Container(); - const posX = (width - transformedSprite.width) / 2; - const posY = (height - transformedSprite.height) / 2; - transformedSprite.position.set(posX, posY); - finalContainer.addChild(transformedSprite); - const renderTexture = RenderTexture.create({ width, height, resolution: 1 }); - GetRenderer().render({ container: finalContainer, target: renderTexture, clear: true }); + const bounds = transformedSprite.getBounds(); + const renderWidth = Math.ceil(bounds.width); + const renderHeight = Math.ceil(bounds.height); + + transformedSprite.position.set(-bounds.x, -bounds.y); + + const renderTexture = RenderTexture.create({ width: renderWidth, height: renderHeight, resolution: 1 }); + GetRenderer().render({ container: transformedSprite, target: renderTexture, clear: true }); return renderTexture; } From 2138d3a820ca13f0456734efad096d650ac5fb96 Mon Sep 17 00:00:00 2001 From: duckietm Date: Tue, 31 Mar 2026 14:54:42 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=86=99=20fix=20some=20metrics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FurnitureDynamicThumbnailVisualization.ts | 1 + .../FurnitureYoutubeVisualization.ts | 7 ++ .../IsometricImageFurniVisualization.ts | 99 ++++++++++--------- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts index 47f3959..5e320b1 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts @@ -28,6 +28,7 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV if (image.complete && image.width > 0 && image.height > 0) { const texture = Texture.from(image); texture.source.scaleMode = 'linear'; + this.setThumbnailImages(texture, thumbnailUrl); } else { this.setThumbnailImages(null); diff --git a/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts index 1b1a544..73cb319 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureYoutubeVisualization.ts @@ -5,6 +5,13 @@ export class FurnitureYoutubeVisualization extends FurnitureDynamicThumbnailVisu { protected static THUMBNAIL_URL: string = 'THUMBNAIL_URL'; + constructor() + { + super(); + + this._hasOutline = false; + } + protected getThumbnailURL(): string { if(!this.object) return null; diff --git a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts index f3f79ce..84c2192 100644 --- a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts +++ b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts @@ -1,15 +1,16 @@ import { IGraphicAsset } from '@nitrots/api'; -import { GetRenderer, TextureUtils } from '@nitrots/utils'; -import { Container, Graphics, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js'; +import { GetRenderer } from '@nitrots/utils'; +import { Container, Matrix, Sprite, Texture, RenderTexture } from 'pixi.js'; import { FurnitureAnimatedVisualization } from './FurnitureAnimatedVisualization'; export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualization { protected static THUMBNAIL: string = 'THUMBNAIL'; - private _thumbnailAssetNameNormal: string; private _thumbnailImageNormal: Texture; private _thumbnailDirection: number; private _thumbnailChanged: boolean; + private _thumbnailLayerId: number; + private _thumbnailTexture: Texture; private _uniqueId: string; private _photoUrl: string; protected _hasOutline: boolean; @@ -17,10 +18,11 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza constructor() { super(); - this._thumbnailAssetNameNormal = null; this._thumbnailImageNormal = null; this._thumbnailDirection = -1; this._thumbnailChanged = false; + this._thumbnailLayerId = -1; + this._thumbnailTexture = null; this._uniqueId = `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; this._photoUrl = null; } @@ -59,7 +61,8 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza if (this._thumbnailImageNormal) { this.addThumbnailAsset(this._thumbnailImageNormal, 64); } else { - this.asset.disposeAsset(`${this.getThumbnailAssetName(64)}-${this._uniqueId}`); + this._thumbnailTexture = null; + this._thumbnailLayerId = -1; } this._thumbnailChanged = false; @@ -73,21 +76,13 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza const layerTag = this.getLayerTag(scale, this.direction, layerId); if (layerTag === IsometricImageFurniVisualization.THUMBNAIL) { + this._thumbnailLayerId = layerId; + const assetName = (this.cacheSpriteAssetName(scale, layerId, false) + this.getFrameNumber(scale, layerId)); const asset = this.getAsset(assetName, layerId); - const thumbnailAssetName = `${this.getThumbnailAssetName(scale)}-${this._uniqueId}`; - const transformedTexture = this.generateTransformedThumbnail(k, asset || { width: 64, height: 64 }); - // Use the original asset's registered offsets so the thumbnail is drawn at the - // furniture-defined sprite position. Fall back to centering when no asset exists. - const offsetX = asset ? asset.offsetX : -Math.floor(transformedTexture.width / 2); - const offsetY = asset ? asset.offsetY : -Math.floor(transformedTexture.height / 2); - - this.asset.addAsset(thumbnailAssetName, transformedTexture, true, offsetX, offsetY, false, false); - - const placedSprite = this.getSprite(layerId); - if (placedSprite) { - placedSprite.texture = transformedTexture; + if (asset) { + this._thumbnailTexture = this.generateTransformedThumbnail(k, asset); } return; @@ -97,9 +92,20 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza } } + protected updateSprite(scale: number, layerId: number): void { + super.updateSprite(scale, layerId); + + if (this._thumbnailTexture && this._thumbnailLayerId === layerId) { + const sprite = this.getSprite(layerId); + if (sprite) { + sprite.texture = this._thumbnailTexture; + } + } + } + protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture { - const assetWidth = asset?.width || 64; - const assetHeight = asset?.height || 64; + const assetWidth = asset.width; + const assetHeight = asset.height; if(this._hasOutline) { @@ -126,8 +132,10 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza texture.source.scaleMode = 'linear'; - const scaleX = assetWidth / texture.width; - const scaleY = assetHeight / texture.height; + const texW = texture.width; + const texH = texture.height; + const scaleX = assetWidth / texW; + const scaleY = assetHeight / texH; const matrix = new Matrix(); @@ -139,7 +147,7 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza matrix.c = 0; matrix.d = (scaleY / 1.6); matrix.tx = 0; - matrix.ty = (0.5 * scaleX * texture.width); + matrix.ty = (0.5 * scaleX * texW); break; case 0: case 4: @@ -159,34 +167,37 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza matrix.ty = 0; } + // Calculate transformed corners manually for accurate bounds + const corners = [ + { x: matrix.tx, y: matrix.ty }, + { x: matrix.a * texW + matrix.tx, y: matrix.b * texW + matrix.ty }, + { x: matrix.c * texH + matrix.tx, y: matrix.d * texH + matrix.ty }, + { x: matrix.a * texW + matrix.c * texH + matrix.tx, y: matrix.b * texW + matrix.d * texH + matrix.ty } + ]; + + let minX = corners[0].x, minY = corners[0].y; + let maxX = corners[0].x, maxY = corners[0].y; + + for (const corner of corners) { + if (corner.x < minX) minX = corner.x; + if (corner.y < minY) minY = corner.y; + if (corner.x > maxX) maxX = corner.x; + if (corner.y > maxY) maxY = corner.y; + } + + const renderWidth = Math.ceil(maxX - minX); + const renderHeight = Math.ceil(maxY - minY); + + matrix.tx -= minX; + matrix.ty -= minY; + const transformedSprite = new Sprite(texture); transformedSprite.setFromMatrix(matrix); - const bounds = transformedSprite.getBounds(); - const renderWidth = Math.ceil(bounds.width); - const renderHeight = Math.ceil(bounds.height); - - transformedSprite.position.set(-bounds.x, -bounds.y); - const renderTexture = RenderTexture.create({ width: renderWidth, height: renderHeight, resolution: 1 }); GetRenderer().render({ container: transformedSprite, target: renderTexture, clear: true }); return renderTexture; } - protected getSpriteAssetName(scale: number, layerId: number): string { - if (this._thumbnailImageNormal && (this.getLayerTag(scale, this.direction, layerId) === IsometricImageFurniVisualization.THUMBNAIL)) { - return `${this.getThumbnailAssetName(scale)}-${this._uniqueId}`; - } - - return super.getSpriteAssetName(scale, layerId); - } - - protected getThumbnailAssetName(scale: number): string { - return this.cacheSpriteAssetName(scale, 2, false) + this.getFrameNumber(scale, 2); - } - - protected getFullThumbnailAssetName(k: number, _arg_2: number): string { - return [this._type, k, 'thumb', _arg_2].join('_'); - } -} \ No newline at end of file +} From 44cb722f54dd02369318fdcee4a03140622fc32e Mon Sep 17 00:00:00 2001 From: duckietm Date: Tue, 31 Mar 2026 15:20:28 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=9D=20Fixed=20some=20memory=20prob?= =?UTF-8?q?lems?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RenderTexture leak in IsometricImageFurniVisualization, every direction change or thumbnail update leaked a GPU RenderTexture. Image object leak in FurnitureDynamicThumbnailVisualization, now clears callbacks and src after use. Image object leak in FurnitureBadgeDisplayVisualization, image objects never cleaned up --- .../FurnitureBadgeDisplayVisualization.ts | 9 +++++++++ .../FurnitureDynamicThumbnailVisualization.ts | 6 ++++++ .../IsometricImageFurniVisualization.ts | 17 ++++++++++++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts index 7caa949..8e39508 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts @@ -340,6 +340,15 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali ctx.clearRect(0, 0, badgeCanvas.width, badgeCanvas.height); ctx.drawImage(img, 0, 0, badgeCanvas.width, badgeCanvas.height); tex.source.update(); + img.onload = null; + img.onerror = null; + img.src = ''; + }; + img.onerror = () => + { + img.onload = null; + img.onerror = null; + img.src = ''; }; img.src = badgeUrl; } diff --git a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts index 5e320b1..810ca9d 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts @@ -33,10 +33,16 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV } else { this.setThumbnailImages(null); } + image.onload = null; + image.onerror = null; + image.src = ''; }; image.onerror = () => { this.setThumbnailImages(null); + image.onload = null; + image.onerror = null; + image.src = ''; }; } else { this.setThumbnailImages(null); diff --git a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts index 84c2192..a5ee557 100644 --- a/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts +++ b/packages/room/src/object/visualization/furniture/IsometricImageFurniVisualization.ts @@ -61,6 +61,9 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza if (this._thumbnailImageNormal) { this.addThumbnailAsset(this._thumbnailImageNormal, 64); } else { + if (this._thumbnailTexture instanceof RenderTexture) { + this._thumbnailTexture.destroy(true); + } this._thumbnailTexture = null; this._thumbnailLayerId = -1; } @@ -82,6 +85,9 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza const asset = this.getAsset(assetName, layerId); if (asset) { + if (this._thumbnailTexture instanceof RenderTexture) { + this._thumbnailTexture.destroy(true); + } this._thumbnailTexture = this.generateTransformedThumbnail(k, asset); } @@ -106,6 +112,7 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza protected generateTransformedThumbnail(texture: Texture, asset: IGraphicAsset): Texture { const assetWidth = asset.width; const assetHeight = asset.height; + let outlineTexture: RenderTexture = null; if(this._hasOutline) { @@ -124,10 +131,10 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza container.addChild(background, imageSprite); - const flatRenderTexture = RenderTexture.create({ width: bgWidth, height: bgHeight, resolution: 1 }); - GetRenderer().render({ container, target: flatRenderTexture, clear: true }); + outlineTexture = RenderTexture.create({ width: bgWidth, height: bgHeight, resolution: 1 }); + GetRenderer().render({ container, target: outlineTexture, clear: true }); - texture = flatRenderTexture; + texture = outlineTexture; } texture.source.scaleMode = 'linear'; @@ -197,6 +204,10 @@ export class IsometricImageFurniVisualization extends FurnitureAnimatedVisualiza const renderTexture = RenderTexture.create({ width: renderWidth, height: renderHeight, resolution: 1 }); GetRenderer().render({ container: transformedSprite, target: renderTexture, clear: true }); + if (outlineTexture) { + outlineTexture.destroy(true); + } + return renderTexture; } From fd74fd323bae9b67b12de93eb38bab44d711e0c2 Mon Sep 17 00:00:00 2001 From: duckietm Date: Tue, 31 Mar 2026 16:03:21 +0200 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=86=99=20Some=20minor=20mem=20tweaks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/avatar/src/AvatarImage.ts | 2 + .../camera/src/RoomCameraWidgetManager.ts | 7 +- .../communication/src/CommunicationManager.ts | 3 +- .../FurnitureBadgeDisplayVisualization.ts | 7 +- .../FurnitureDynamicThumbnailVisualization.ts | 2 - .../object/visualization/room/RoomPlane.ts | 349 +++++++++--------- .../session/src/badge/BadgeImageManager.ts | 1 + 7 files changed, 198 insertions(+), 173 deletions(-) diff --git a/packages/avatar/src/AvatarImage.ts b/packages/avatar/src/AvatarImage.ts index b98e2e3..4cc52cf 100644 --- a/packages/avatar/src/AvatarImage.ts +++ b/packages/avatar/src/AvatarImage.ts @@ -285,6 +285,8 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener const container = this.buildAvatarContainer(avatarCanvas, setType); + if(!container) return null; + GetRenderer().render({ target: this._activeTexture, container: container, diff --git a/packages/camera/src/RoomCameraWidgetManager.ts b/packages/camera/src/RoomCameraWidgetManager.ts index 1d28a4c..85b7067 100644 --- a/packages/camera/src/RoomCameraWidgetManager.ts +++ b/packages/camera/src/RoomCameraWidgetManager.ts @@ -117,7 +117,12 @@ export class RoomCameraWidgetManager implements IRoomCameraWidgetManager TextureUtils.writeToTexture(container, renderTexture); - return await TextureUtils.generateImage(renderTexture); + const image = await TextureUtils.generateImage(renderTexture); + + container.destroy({ children: true }); + renderTexture.destroy(true); + + return image; } public get effects(): Map diff --git a/packages/communication/src/CommunicationManager.ts b/packages/communication/src/CommunicationManager.ts index 0fa6a90..3718463 100644 --- a/packages/communication/src/CommunicationManager.ts +++ b/packages/communication/src/CommunicationManager.ts @@ -62,7 +62,8 @@ export class CommunicationManager implements ICommunicationManager t.shadowColor = 'blue'; t.fillRect(-20, 10, 234, 5); const i = e.toDataURL(); - document.body.appendChild(e); + e.width = 0; + e.height = 0; let r = 0; if (i.length === 0) return 'nothing!'; for (let n = 0; n < i.length; n++) { diff --git a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts index 8e39508..7e344e8 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts @@ -283,6 +283,8 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali tempCtx.putImageData(patchData, 0, 0); accCtx.drawImage(tempCanvas, frame.dims.left, frame.dims.top); + tempCanvas.width = 0; + tempCanvas.height = 0; // Create a new canvas for this frame and create a texture from it const frameCanvas = document.createElement('canvas'); @@ -299,6 +301,9 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali frameDelays.push(frame.delay || 10); } + accCanvas.width = 0; + accCanvas.height = 0; + // Create AnimatedSprite with frame textures if(this._frameTextures.length > 1) { @@ -342,13 +347,11 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali tex.source.update(); img.onload = null; img.onerror = null; - img.src = ''; }; img.onerror = () => { img.onload = null; img.onerror = null; - img.src = ''; }; img.src = badgeUrl; } diff --git a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts index 810ca9d..4c0697a 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureDynamicThumbnailVisualization.ts @@ -35,14 +35,12 @@ export class FurnitureDynamicThumbnailVisualization extends IsometricImageFurniV } image.onload = null; image.onerror = null; - image.src = ''; }; image.onerror = () => { this.setThumbnailImages(null); image.onload = null; image.onerror = null; - image.src = ''; }; } else { this.setThumbnailImages(null); diff --git a/packages/room/src/object/visualization/room/RoomPlane.ts b/packages/room/src/object/visualization/room/RoomPlane.ts index a1391bd..3556447 100644 --- a/packages/room/src/object/visualization/room/RoomPlane.ts +++ b/packages/room/src/object/visualization/room/RoomPlane.ts @@ -867,203 +867,218 @@ export class RoomPlane implements IRoomPlane } private renderWindowReflections(): void +{ + if(!this._planeTexture || !this._leftSide || !this._rightSide || !this._normal) return; + + if(this._leftSide.length <= 0 || this._rightSide.length <= 0) return; + + const now = Date.now(); + const fadeDurationMs = 150; + const avatars = RoomWindowReflectionState.getAvatars(); + const canvasWidth = this._landscapeRenderWidth; + const canvasHeight = this._landscapeRenderHeight; + + if(canvasWidth <= 0 || canvasHeight <= 0) return; + + const container = new Container(); + const visibleAvatarIds = new Set(); + + const addReflectionSprite = ( + texture: Texture, + oppositeTexture: Texture, + location: IVector3D, + alpha: number, + verticalOffset: number = 0, + direction: number = 0, + avatarId: number = -1 + ): boolean => { - if(!this._planeTexture || !this._leftSide || !this._rightSide || !this._normal) return; + if(!texture?.source || texture.source.destroyed || !texture.source.style || !location || alpha < 0) + return false; - if(this._leftSide.length <= 0 || this._rightSide.length <= 0) return; + const relative = Vector3d.dif(location, this._location); + const planeDistance = Math.abs(Vector3d.scalarProjection(relative, this._normal)); - const now = Date.now(); - const fadeDurationMs = 150; - const avatars = RoomWindowReflectionState.getAvatars(); - const canvasWidth = this._landscapeRenderWidth; - const canvasHeight = this._landscapeRenderHeight; + if(planeDistance > 0.8) return false; - if(canvasWidth <= 0 || canvasHeight <= 0) return; + const leftSideLoc = Vector3d.scalarProjection(relative, this._leftSide); + const rightSideLoc = Vector3d.scalarProjection(relative, this._rightSide); - const container = new Container(); - const visibleAvatarIds = new Set(); - - const addReflectionSprite = (texture: Texture, oppositeTexture: Texture, location: IVector3D, alpha: number, verticalOffset: number = 0, direction: number = 0, avatarId: number = -1): boolean => { - if(!texture?.source || texture.source.destroyed || !texture.source.style || !location || alpha < 0) return false; - - const relative = Vector3d.dif(location, this._location); - const planeDistance = Math.abs(Vector3d.scalarProjection(relative, this._normal)); - - if(planeDistance > 0.8) return false; - - const leftSideLoc = Vector3d.scalarProjection(relative, this._leftSide); - const rightSideLoc = Vector3d.scalarProjection(relative, this._rightSide); - - const closestMask = this._windowMasks.reduce((best, mask) => { - const score = Math.abs(mask.leftSideLoc - leftSideLoc) + Math.abs(mask.rightSideLoc - rightSideLoc); - - if(!best || (score < best.score)) return { mask, score }; - - return best; - }, null as { mask: { leftSideLoc: number; rightSideLoc: number }; score: number } | null); - - if(!closestMask || (closestMask.score > 3)) return false; - - const x = (canvasWidth - ((canvasWidth * leftSideLoc) / this._leftSide.length)); - const y = (canvasHeight - ((canvasHeight * rightSideLoc) / this._rightSide.length)) + verticalOffset; - - const toPlaneX = (this._location.x - location.x); - const toPlaneY = (this._location.y - location.y); - const toPlaneLength = Math.hypot(toPlaneX, toPlaneY); - - const facingRadians = ((((direction - 90) % 360) + 360) % 360) * (Math.PI / 180); - const facingX = Math.cos(facingRadians); - const facingY = Math.sin(facingRadians); - const facingWindow = (toPlaneLength > 0.001) - ? (((facingX * toPlaneX) + (facingY * toPlaneY)) / toPlaneLength) > 0.5 - : false; - - const deltaLeft = Math.abs(closestMask.mask.leftSideLoc - leftSideLoc); - const deltaRight = Math.abs(closestMask.mask.rightSideLoc - rightSideLoc); - - const isInFrontOfWindow = ((closestMask.score <= 2) && ((deltaLeft <= 0.9) || (deltaRight <= 0.9))); - const shouldMirror = isInFrontOfWindow; - - const normal2DLength = Math.hypot(this._normal.x, this._normal.y); - const normalX = (normal2DLength > 0.0001) ? (this._normal.x / normal2DLength) : 0; - const normalY = (normal2DLength > 0.0001) ? (this._normal.y / normal2DLength) : 0; - const normalFacingDot = Math.abs((facingX * normalX) + (facingY * normalY)); - - const transitionLow = 0.6; - const transitionHigh = 0.8; - let oppositeWeight = 0; - - if(shouldMirror && oppositeTexture) - { - if(normalFacingDot >= transitionHigh) oppositeWeight = 1; - else if(normalFacingDot > transitionLow) oppositeWeight = (normalFacingDot - transitionLow) / (transitionHigh - transitionLow); - } - - if(oppositeWeight < 1) - { - const sprite = new Sprite(texture); - sprite.anchor.set(0.5, 1); - sprite.position.set(Math.trunc(x), Math.trunc(y)); - sprite.scale.set(1, 1); - sprite.tint = 0xCFE3FF; - sprite.alpha = alpha * (1 - oppositeWeight); - container.addChild(sprite); - } - - if(oppositeWeight > 0 && oppositeTexture) - { - const sprite = new Sprite(oppositeTexture); - sprite.anchor.set(0.5, 1); - sprite.position.set(Math.trunc(x), Math.trunc(y)); - sprite.scale.set(1, 1); - sprite.tint = 0xCFE3FF; - sprite.alpha = alpha * oppositeWeight; - container.addChild(sprite); - } - - return true; - }; - - for(const avatar of avatars) + const closestMask = this._windowMasks.reduce((best, mask) => { - if(!avatar?.texture?.source || avatar.texture.source.destroyed || !avatar.texture.source.style || !avatar.location) continue; + const score = Math.abs(mask.leftSideLoc - leftSideLoc) + Math.abs(mask.rightSideLoc - rightSideLoc); - let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id); + if(!best || (score < best.score)) return { mask, score }; - if(firstSeenAt === undefined) - { - firstSeenAt = now; - } + return best; + }, null as { mask: { leftSideLoc: number; rightSideLoc: number }; score: number } | null); - const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt))); - const progress = (elapsed / fadeDurationMs); - const alpha = (0.4 * progress); + if(!closestMask || (closestMask.score > 3)) return false; - if(!addReflectionSprite(avatar.texture, avatar.oppositeTexture, avatar.location, alpha, avatar.verticalOffset || 0, avatar.direction || 0, avatar.id)) continue; + const x = (canvasWidth - ((canvasWidth * leftSideLoc) / this._leftSide.length)); + const y = (canvasHeight - ((canvasHeight * rightSideLoc) / this._rightSide.length)) + verticalOffset; - if(!this._windowReflectionFirstSeenAt.has(avatar.id)) this._windowReflectionFirstSeenAt.set(avatar.id, firstSeenAt); + const toPlaneX = (this._location.x - location.x); + const toPlaneY = (this._location.y - location.y); + const toPlaneLength = Math.hypot(toPlaneX, toPlaneY); - visibleAvatarIds.add(avatar.id); - this._windowReflectionFadeOut.delete(avatar.id); + const facingRadians = ((((direction - 90) % 360) + 360) % 360) * (Math.PI / 180); + const facingX = Math.cos(facingRadians); + const facingY = Math.sin(facingRadians); + const facingWindow = (toPlaneLength > 0.001) + ? (((facingX * toPlaneX) + (facingY * toPlaneY)) / toPlaneLength) > 0.5 + : false; - const storedLocation = new Vector3d(); - storedLocation.assign(avatar.location); + const deltaLeft = Math.abs(closestMask.mask.leftSideLoc - leftSideLoc); + const deltaRight = Math.abs(closestMask.mask.rightSideLoc - rightSideLoc); - this._windowReflectionLastVisible.set(avatar.id, { - texture: avatar.texture, - oppositeTexture: avatar.oppositeTexture, - location: storedLocation, - verticalOffset: avatar.verticalOffset || 0, - direction: avatar.direction || 0 - }); + const isInFrontOfWindow = ((closestMask.score <= 2) && ((deltaLeft <= 0.9) || (deltaRight <= 0.9))); + const shouldMirror = isInFrontOfWindow; + + const normal2DLength = Math.hypot(this._normal.x, this._normal.y); + const normalX = (normal2DLength > 0.0001) ? (this._normal.x / normal2DLength) : 0; + const normalY = (normal2DLength > 0.0001) ? (this._normal.y / normal2DLength) : 0; + const normalFacingDot = Math.abs((facingX * normalX) + (facingY * normalY)); + + const transitionLow = 0.6; + const transitionHigh = 0.8; + let oppositeWeight = 0; + + if(shouldMirror && oppositeTexture) + { + if(normalFacingDot >= transitionHigh) oppositeWeight = 1; + else if(normalFacingDot > transitionLow) + oppositeWeight = (normalFacingDot - transitionLow) / (transitionHigh - transitionLow); } - for(const [id, lastVisible] of this._windowReflectionLastVisible) + if(oppositeWeight < 1) { - if(visibleAvatarIds.has(id) || this._windowReflectionFadeOut.has(id)) continue; - - if(!lastVisible.texture?.source || lastVisible.texture.source.destroyed || !lastVisible.texture.source.style) - { - this._windowReflectionLastVisible.delete(id); - this._windowReflectionFirstSeenAt.delete(id); - - continue; - } - - this._windowReflectionFadeOut.set(id, { - texture: lastVisible.texture, - oppositeTexture: lastVisible.oppositeTexture, - location: lastVisible.location, - verticalOffset: lastVisible.verticalOffset, - direction: lastVisible.direction, - startedAt: now - }); - - this._windowReflectionLastVisible.delete(id); - this._windowReflectionFirstSeenAt.delete(id); + const sprite = new Sprite(texture); + sprite.anchor.set(0.5, 1); + sprite.position.set(Math.trunc(x), Math.trunc(y)); + sprite.tint = 0xCFE3FF; + sprite.alpha = alpha * (1 - oppositeWeight); + container.addChild(sprite); } - for(const [id, fadeOut] of this._windowReflectionFadeOut) + if(oppositeWeight > 0 && oppositeTexture) { - const elapsed = (now - fadeOut.startedAt); - - if(elapsed >= fadeDurationMs) - { - this._windowReflectionFadeOut.delete(id); - - continue; - } - - const alpha = (0.4 * (1 - (elapsed / fadeDurationMs))); - - if(!addReflectionSprite(fadeOut.texture, fadeOut.oppositeTexture, fadeOut.location, alpha, fadeOut.verticalOffset, fadeOut.direction, id)) this._windowReflectionFadeOut.delete(id); + const sprite = new Sprite(oppositeTexture); + sprite.anchor.set(0.5, 1); + sprite.position.set(Math.trunc(x), Math.trunc(y)); + sprite.tint = 0xCFE3FF; + sprite.alpha = alpha * oppositeWeight; + container.addChild(sprite); } - if(!container.children.length) - { - container.destroy({ children: true }); + return true; + }; - if(!avatars.length) - { - this._windowReflectionFirstSeenAt.clear(); - this._windowReflectionLastVisible.clear(); - } + for(const avatar of avatars) + { + if(!avatar?.texture?.source || avatar.texture.source.destroyed || !avatar.texture.source.style || !avatar.location) + continue; - return; - } + let firstSeenAt = this._windowReflectionFirstSeenAt.get(avatar.id); - if(this._maskFilter) container.filters = [this._maskFilter]; + if(firstSeenAt === undefined) firstSeenAt = now; - GetRenderer().render({ - target: this._planeTexture, - container, - transform: this.getMatrixForDimensions(canvasWidth, canvasHeight), - clear: false + const elapsed = Math.min(fadeDurationMs, Math.max(0, (now - firstSeenAt))); + const alpha = (0.4 * (elapsed / fadeDurationMs)); + + if(!addReflectionSprite( + avatar.texture, + avatar.oppositeTexture, + avatar.location, + alpha, + avatar.verticalOffset || 0, + avatar.direction || 0, + avatar.id)) + continue; + + if(!this._windowReflectionFirstSeenAt.has(avatar.id)) + this._windowReflectionFirstSeenAt.set(avatar.id, firstSeenAt); + + visibleAvatarIds.add(avatar.id); + this._windowReflectionFadeOut.delete(avatar.id); + + const storedLocation = new Vector3d(); + storedLocation.assign(avatar.location); + + this._windowReflectionLastVisible.set(avatar.id, { + texture: avatar.texture, + oppositeTexture: avatar.oppositeTexture, + location: storedLocation, + verticalOffset: avatar.verticalOffset || 0, + direction: avatar.direction || 0 + }); + } + + // move to fade-out (NO destruction) + for(const [id, lastVisible] of this._windowReflectionLastVisible) + { + if(visibleAvatarIds.has(id) || this._windowReflectionFadeOut.has(id)) continue; + + this._windowReflectionFadeOut.set(id, { + ...lastVisible, + startedAt: now }); - container.destroy({ children: true }); + this._windowReflectionLastVisible.delete(id); + this._windowReflectionFirstSeenAt.delete(id); } + // fade-out rendering (NO destruction) + for(const [id, fadeOut] of this._windowReflectionFadeOut) + { + const elapsed = (now - fadeOut.startedAt); + + if(elapsed >= fadeDurationMs) + { + this._windowReflectionFadeOut.delete(id); + continue; + } + + const alpha = (0.4 * (1 - (elapsed / fadeDurationMs))); + + if(!addReflectionSprite( + fadeOut.texture, + fadeOut.oppositeTexture, + fadeOut.location, + alpha, + fadeOut.verticalOffset, + fadeOut.direction, + id)) + { + this._windowReflectionFadeOut.delete(id); + } + } + + if(!container.children.length) + { + container.destroy({ children: true }); + + if(!avatars.length) + { + this._windowReflectionFirstSeenAt.clear(); + this._windowReflectionLastVisible.clear(); + } + + return; + } + + if(this._maskFilter) container.filters = [this._maskFilter]; + + GetRenderer().render({ + target: this._planeTexture, + container, + transform: this.getMatrixForDimensions(canvasWidth, canvasHeight), + clear: false + }); + + container.destroy({ children: true }); + } + private updateCorners(geometry: IRoomGeometry): void { this._cornerA.assign(geometry.getScreenPosition(this._location)); diff --git a/packages/session/src/badge/BadgeImageManager.ts b/packages/session/src/badge/BadgeImageManager.ts index 6a51f7e..9bb5647 100644 --- a/packages/session/src/badge/BadgeImageManager.ts +++ b/packages/session/src/badge/BadgeImageManager.ts @@ -248,6 +248,7 @@ export class BadgeImageManager if(!renderedLayers) return false; const texture = TextureUtils.generateTexture(container); + container.destroy({ children: true }); GetAssetManager().setTexture(groupBadge.code, texture); GetEventDispatcher().dispatchEvent(new BadgeImageReadyEvent(groupBadge.code, texture));