diff --git a/packages/api/src/nitro/room/IRoomEngine.ts b/packages/api/src/nitro/room/IRoomEngine.ts index f153dc0..986e7fb 100644 --- a/packages/api/src/nitro/room/IRoomEngine.ts +++ b/packages/api/src/nitro/room/IRoomEngine.ts @@ -65,6 +65,7 @@ export interface IRoomEngine getFurnitureWallName(typeId: number, extra?: string): string; selectRoomObject(roomId: number, objectId: number, objectCategory: number): void; setSelectedAvatar(roomId: number, objectId: number): void; + clearSelectedAvatar(roomId: number): void; cancelRoomObjectInsert(): void; getPetColorResult(petIndex: number, paletteIndex: number): IPetColorResult; getPetColorResultsForTag(petIndex: number, tagName: string): IPetColorResult[]; diff --git a/packages/avatar/src/AvatarAssetDownloadManager.ts b/packages/avatar/src/AvatarAssetDownloadManager.ts index 347108f..bd84625 100644 --- a/packages/avatar/src/AvatarAssetDownloadManager.ts +++ b/packages/avatar/src/AvatarAssetDownloadManager.ts @@ -97,7 +97,7 @@ export class AvatarAssetDownloadManager const downloadLibrary = new AvatarAssetDownloadLibrary(libraryName, revision, downloadUrl, this._assets); - for(const part of library.parts) + for(const part of (library.parts || [])) { const id = (part.id as string); const type = (part.type as string); diff --git a/packages/avatar/src/cache/AvatarImageCache.ts b/packages/avatar/src/cache/AvatarImageCache.ts index 6c8381e..a2d457e 100644 --- a/packages/avatar/src/cache/AvatarImageCache.ts +++ b/packages/avatar/src/cache/AvatarImageCache.ts @@ -24,6 +24,7 @@ export class AvatarImageCache private _canvas: AvatarCanvas; private _disposed: boolean; private _geometryType: string; + private _defaultAction: string = 'std'; private _unionImages: ImageData[]; private _matrix: Matrix; @@ -140,6 +141,7 @@ export class AvatarImageCache { this._geometryType = k; this._canvas = null; + this._defaultAction = (k === GeometryType.HORIZONTAL) ? 'lay' : 'std'; return; } @@ -147,7 +149,8 @@ export class AvatarImageCache this.disposeInactiveActions(0); this._geometryType = k; - this._canvas = null; + this._canvas = null; + this._defaultAction = (k === GeometryType.HORIZONTAL) ? 'lay' : 'std'; } public getImageContainer(key: string, frameNumber: number, forceRefresh: boolean = false): AvatarImageBodyPartContainer @@ -330,7 +333,19 @@ export class AvatarImageCache if(!asset) { - assetName = (this._scale + '_std_' + partType + '_' + partId + '_' + assetDirection + '_0'); + assetName = (this._scale + '_' + assetPartDefinition + '_' + partType + '_' + partId + '_' + assetDirection + '_0'); + asset = this._assets.getAsset(assetName); + } + + if(!asset) + { + assetName = (this._scale + '_' + this._defaultAction + '_' + partType + '_' + partId + '_' + assetDirection + '_' + frameNumber); + asset = this._assets.getAsset(assetName); + } + + if(!asset) + { + assetName = (this._scale + '_' + this._defaultAction + '_' + partType + '_' + partId + '_' + assetDirection + '_0'); asset = this._assets.getAsset(assetName); } diff --git a/packages/avatar/src/data/HabboAvatarAnimations.ts b/packages/avatar/src/data/HabboAvatarAnimations.ts index 2058b64..64710aa 100644 --- a/packages/avatar/src/data/HabboAvatarAnimations.ts +++ b/packages/avatar/src/data/HabboAvatarAnimations.ts @@ -1,5 +1,475 @@ export const HabboAvatarAnimations = { 'animations': [ + { + 'id': 'Default', + 'parts': [ + { + 'setType': 'bd', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'bds', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'lg', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'sh', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'ch', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'cc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'lc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'rc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'lh', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'lhs', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'rh', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'rhs', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'ls', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'rs', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'he', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + }, + { + 'setType': 'wa', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + } + ] + }, + { + 'id': 'Sit', + 'parts': [ + { + 'setType': 'bd', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'bds', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'lg', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'sh', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'ch', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'cc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'lc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'rc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'sit' }, + { 'number': 1, 'assetPartDefinition': 'sit' }, + { 'number': 2, 'assetPartDefinition': 'sit' }, + { 'number': 3, 'assetPartDefinition': 'sit' }, + { 'number': 4, 'assetPartDefinition': 'sit' }, + { 'number': 5, 'assetPartDefinition': 'sit' }, + { 'number': 6, 'assetPartDefinition': 'sit' }, + { 'number': 7, 'assetPartDefinition': 'sit' } + ] + }, + { + 'setType': 'wa', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'std' }, + { 'number': 1, 'assetPartDefinition': 'std' }, + { 'number': 2, 'assetPartDefinition': 'std' }, + { 'number': 3, 'assetPartDefinition': 'std' }, + { 'number': 4, 'assetPartDefinition': 'std' }, + { 'number': 5, 'assetPartDefinition': 'std' }, + { 'number': 6, 'assetPartDefinition': 'std' }, + { 'number': 7, 'assetPartDefinition': 'std' } + ] + } + ] + }, + { + 'id': 'Lay', + 'parts': [ + { + 'setType': 'bd', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'bds', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'lg', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'sh', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'ch', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'cc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'lc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'rc', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'wa', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + }, + { + 'setType': 'he', + 'frames': [ + { 'number': 0, 'assetPartDefinition': 'lay' }, + { 'number': 1, 'assetPartDefinition': 'lay' }, + { 'number': 2, 'assetPartDefinition': 'lay' }, + { 'number': 3, 'assetPartDefinition': 'lay' }, + { 'number': 4, 'assetPartDefinition': 'lay' }, + { 'number': 5, 'assetPartDefinition': 'lay' }, + { 'number': 6, 'assetPartDefinition': 'lay' }, + { 'number': 7, 'assetPartDefinition': 'lay' } + ] + } + ] + }, { 'id': 'Move', 'parts': [ diff --git a/packages/communication/src/NitroMessages.ts b/packages/communication/src/NitroMessages.ts index ced7b60..23eac98 100644 --- a/packages/communication/src/NitroMessages.ts +++ b/packages/communication/src/NitroMessages.ts @@ -93,6 +93,9 @@ export class NitroMessages implements IMessageConfiguration this._events.set(IncomingHeader.TARGET_OFFER_NOT_FOUND, TargetedOfferNotFoundEvent); this._events.set(IncomingHeader.REDEEM_VOUCHER_ERROR, VoucherRedeemErrorMessageEvent); this._events.set(IncomingHeader.REDEEM_VOUCHER_OK, VoucherRedeemOkMessageEvent); + + // COMMANDS + this._events.set(IncomingHeader.AVAILABLE_COMMANDS, AvailableCommandsEvent); // CLIENT this._events.set(IncomingHeader.CLIENT_PING, ClientPingEvent); @@ -587,6 +590,7 @@ export class NitroMessages implements IMessageConfiguration { // CUSTOM PACKETS this._composers.set(OutgoingHeader.CLICK_FURNI, ClickFurniMessageComposer); + this._composers.set(OutgoingHeader.CLICK_USER, ClickUserMessageComposer); // AUTHENTICATION this._composers.set(OutgoingHeader.AUTHENTICATION, AuthenticationMessageComposer); diff --git a/packages/communication/src/index.ts b/packages/communication/src/index.ts index 5f56f6c..5553756 100644 --- a/packages/communication/src/index.ts +++ b/packages/communication/src/index.ts @@ -15,6 +15,7 @@ export * from './messages/incoming/camera'; export * from './messages/incoming/campaign'; export * from './messages/incoming/catalog'; export * from './messages/incoming/client'; +export * from './messages/incoming/commands'; export * from './messages/incoming/competition'; export * from './messages/incoming/crafting'; export * from './messages/incoming/desktop'; @@ -167,6 +168,7 @@ export * from './messages/parser/camera'; export * from './messages/parser/campaign'; export * from './messages/parser/catalog'; export * from './messages/parser/client'; +export * from './messages/parser/commands'; export * from './messages/parser/competition'; export * from './messages/parser/crafting'; export * from './messages/parser/desktop'; diff --git a/packages/communication/src/messages/incoming/IncomingHeader.ts b/packages/communication/src/messages/incoming/IncomingHeader.ts index 5d7f733..59d3879 100644 --- a/packages/communication/src/messages/incoming/IncomingHeader.ts +++ b/packages/communication/src/messages/incoming/IncomingHeader.ts @@ -7,6 +7,7 @@ export class IncomingHeader public static ACHIEVEMENT_LIST = 305; public static AUTHENTICATED = 2491; public static AUTHENTICATION = -1; + public static AVAILABLE_COMMANDS = 4050; public static AVAILABILITY_STATUS = 2033; public static BUILDERS_CLUB_EXPIRED = 1452; public static CLUB_OFFERS = 2405; diff --git a/packages/communication/src/messages/incoming/commands/AvailableCommandsEvent.ts b/packages/communication/src/messages/incoming/commands/AvailableCommandsEvent.ts new file mode 100644 index 0000000..0cfd3af --- /dev/null +++ b/packages/communication/src/messages/incoming/commands/AvailableCommandsEvent.ts @@ -0,0 +1,16 @@ +import { IMessageEvent } from '@nitrots/api'; +import { MessageEvent } from '@nitrots/events'; +import { AvailableCommandsParser } from '../../parser'; + +export class AvailableCommandsEvent extends MessageEvent implements IMessageEvent +{ + constructor(callBack: Function) + { + super(callBack, AvailableCommandsParser); + } + + public getParser(): AvailableCommandsParser + { + return this.parser as AvailableCommandsParser; + } +} \ No newline at end of file diff --git a/packages/communication/src/messages/incoming/commands/index.ts b/packages/communication/src/messages/incoming/commands/index.ts new file mode 100644 index 0000000..acc402d --- /dev/null +++ b/packages/communication/src/messages/incoming/commands/index.ts @@ -0,0 +1 @@ +export * from './AvailableCommandsEvent'; \ No newline at end of file diff --git a/packages/communication/src/messages/incoming/index.ts b/packages/communication/src/messages/incoming/index.ts index a247dbc..b9b0d3d 100644 --- a/packages/communication/src/messages/incoming/index.ts +++ b/packages/communication/src/messages/incoming/index.ts @@ -8,6 +8,7 @@ export * from './camera'; export * from './campaign'; export * from './catalog'; export * from './client'; +export * from './commands'; export * from './competition'; export * from './crafting'; export * from './desktop'; diff --git a/packages/communication/src/messages/outgoing/OutgoingHeader.ts b/packages/communication/src/messages/outgoing/OutgoingHeader.ts index 74547a1..1408f93 100644 --- a/packages/communication/src/messages/outgoing/OutgoingHeader.ts +++ b/packages/communication/src/messages/outgoing/OutgoingHeader.ts @@ -1,6 +1,7 @@ export class OutgoingHeader { public static CLICK_FURNI = 6002; + public static CLICK_USER = 10020; public static ACHIEVEMENT_LIST = 219; public static AUTHENTICATION = -1; diff --git a/packages/communication/src/messages/outgoing/room/engine/ClickUserMessageComposer.ts b/packages/communication/src/messages/outgoing/room/engine/ClickUserMessageComposer.ts new file mode 100644 index 0000000..a59d16d --- /dev/null +++ b/packages/communication/src/messages/outgoing/room/engine/ClickUserMessageComposer.ts @@ -0,0 +1,21 @@ +import { IMessageComposer } from '@nitrots/api'; + +export class ClickUserMessageComposer implements IMessageComposer> +{ + private _data: ConstructorParameters; + + constructor(roomUnitId: number) + { + this._data = [ roomUnitId ]; + } + + public getMessageArray() + { + return this._data; + } + + public dispose(): void + { + return; + } +} diff --git a/packages/communication/src/messages/outgoing/room/engine/index.ts b/packages/communication/src/messages/outgoing/room/engine/index.ts index ebee1cd..a3aeb1a 100644 --- a/packages/communication/src/messages/outgoing/room/engine/index.ts +++ b/packages/communication/src/messages/outgoing/room/engine/index.ts @@ -2,6 +2,7 @@ export * from './BotPlaceComposer'; export * from './BotRemoveComposer'; export * from './BotSkillSaveComposer'; export * from './ClickFurniMessageComposer'; +export * from './ClickUserMessageComposer'; export * from './CompostPlantMessageComposer'; export * from './GetItemDataComposer'; export * from './HarvestPetMessageComposer'; diff --git a/packages/communication/src/messages/parser/commands/AvailableCommandsParser.ts b/packages/communication/src/messages/parser/commands/AvailableCommandsParser.ts new file mode 100644 index 0000000..5ecb3ca --- /dev/null +++ b/packages/communication/src/messages/parser/commands/AvailableCommandsParser.ts @@ -0,0 +1,37 @@ +import { IMessageDataWrapper, IMessageParser } from '@nitrots/api'; + +export class AvailableCommandsParser implements IMessageParser +{ + private _commands: { key: string; description: string }[]; + + public flush(): boolean + { + this._commands = []; + + return true; + } + + public parse(wrapper: IMessageDataWrapper): boolean + { + if(!wrapper) return false; + + this._commands = []; + + const count = wrapper.readInt(); + + for(let i = 0; i < count; i++) + { + this._commands.push({ + key: wrapper.readString(), + description: wrapper.readString() + }); + } + + return true; + } + + public get commands(): { key: string; description: string }[] + { + return this._commands; + } +} \ No newline at end of file diff --git a/packages/communication/src/messages/parser/commands/index.ts b/packages/communication/src/messages/parser/commands/index.ts new file mode 100644 index 0000000..aa0f4c9 --- /dev/null +++ b/packages/communication/src/messages/parser/commands/index.ts @@ -0,0 +1 @@ +export * from './AvailableCommandsParser'; \ No newline at end of file diff --git a/packages/communication/src/messages/parser/index.ts b/packages/communication/src/messages/parser/index.ts index b807dc4..c1b9bc9 100644 --- a/packages/communication/src/messages/parser/index.ts +++ b/packages/communication/src/messages/parser/index.ts @@ -7,6 +7,7 @@ export * from './camera'; export * from './campaign'; export * from './catalog'; export * from './client'; +export * from './commands'; export * from './competition'; export * from './crafting'; export * from './desktop'; diff --git a/packages/room/src/RoomEngine.ts b/packages/room/src/RoomEngine.ts index ce53d69..19ca14a 100644 --- a/packages/room/src/RoomEngine.ts +++ b/packages/room/src/RoomEngine.ts @@ -2285,8 +2285,6 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService private handleRoomDragging(canvas: IRoomRenderingCanvas, x: number, y: number, type: string, altKey: boolean, ctrlKey: boolean, shiftKey: boolean): boolean { - if(this.isPlayingGame()) return false; - if(this._areaSelectionManager.areaSelectionState === RoomAreaSelectionManager.SELECTING) { this._activeRoomIsDragged = false; @@ -2521,11 +2519,18 @@ export class RoomEngine implements IRoomEngine, IRoomCreator, IRoomEngineService public setSelectedAvatar(roomId: number, objectId: number): void { - if(this._roomObjectEventHandler) return; + if(!this._roomObjectEventHandler) return; this._roomObjectEventHandler.setSelectedAvatar(roomId, objectId, true); } + public clearSelectedAvatar(roomId: number): void + { + if(!this._roomObjectEventHandler) return; + + this._roomObjectEventHandler.clearSelectedAvatar(roomId); + } + public cancelRoomObjectInsert(): void { if(!this._roomObjectEventHandler) return; diff --git a/packages/room/src/RoomMessageHandler.ts b/packages/room/src/RoomMessageHandler.ts index ed836f5..0adbaec 100644 --- a/packages/room/src/RoomMessageHandler.ts +++ b/packages/room/src/RoomMessageHandler.ts @@ -737,18 +737,13 @@ export class RoomMessageHandler this._roomEngine.updateRoomObjectUserLocation(this._currentRoomId, status.id, location, goal, status.canStandUp, height, direction, status.headDirection); this._roomEngine.updateRoomObjectUserFlatControl(this._currentRoomId, status.id, null); - // Save own user's position for reconnection (only when not locked by reconnect flow) + // Save own user's position for reconnection if(status.id === this._ownRoomIndex) { try { - const locked = sessionStorage.getItem('nitro.session.posLocked'); - - if(!locked) - { - sessionStorage.setItem('nitro.session.lastPosX', status.x.toString()); - sessionStorage.setItem('nitro.session.lastPosY', status.y.toString()); - } + sessionStorage.setItem('nitro.session.lastPosX', status.x.toString()); + sessionStorage.setItem('nitro.session.lastPosY', status.y.toString()); } catch(e) { /* ignore */ } } diff --git a/packages/room/src/RoomObjectEventHandler.ts b/packages/room/src/RoomObjectEventHandler.ts index 2725f26..4e7e2bb 100644 --- a/packages/room/src/RoomObjectEventHandler.ts +++ b/packages/room/src/RoomObjectEventHandler.ts @@ -1,5 +1,5 @@ import { IFurnitureStackingHeightMap, ILegacyWallGeometry, IObjectData, IRoomCanvasMouseListener, IRoomEngineServices, IRoomGeometry, IRoomObject, IRoomObjectController, IRoomObjectEventManager, ISelectedRoomObjectData, IVector3D, MouseEventType, RoomObjectCategory, RoomObjectOperationType, RoomObjectPlacementSource, RoomObjectType, RoomObjectUserType, RoomObjectVariable } from '@nitrots/api'; -import { BotPlaceComposer, ClickFurniMessageComposer, FurnitureColorWheelComposer, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureFloorUpdateComposer, FurnitureGroupInfoComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePostItPlaceComposer, FurnitureRandomStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, GetCommunication, GetItemDataComposer, GetResolutionAchievementsMessageComposer, PetMoveComposer, PetPlaceComposer, RemoveWallItemComposer, RoomUnitLookComposer, RoomUnitWalkComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer } from '@nitrots/communication'; +import { BotPlaceComposer, ClickFurniMessageComposer, ClickUserMessageComposer, FurnitureColorWheelComposer, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureFloorUpdateComposer, FurnitureGroupInfoComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePostItPlaceComposer, FurnitureRandomStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, GetCommunication, GetItemDataComposer, GetResolutionAchievementsMessageComposer, PetMoveComposer, PetPlaceComposer, RemoveWallItemComposer, RoomUnitLookComposer, RoomUnitWalkComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer } from '@nitrots/communication'; import { GetConfiguration } from '@nitrots/configuration'; import { GetEventDispatcher, RoomEngineDimmerStateEvent, RoomEngineObjectEvent, RoomEngineObjectPlacedEvent, RoomEngineObjectPlacedOnUserEvent, RoomEngineObjectPlaySoundEvent, RoomEngineRoomAdEvent, RoomEngineSamplePlaybackEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomObjectBadgeAssetEvent, RoomObjectDataRequestEvent, RoomObjectDimmerStateUpdateEvent, RoomObjectEvent, RoomObjectFloorHoleEvent, RoomObjectFurnitureActionEvent, RoomObjectHSLColorEnableEvent, RoomObjectHSLColorEnabledEvent, RoomObjectMouseEvent, RoomObjectMoveEvent, RoomObjectPlaySoundIdEvent, RoomObjectRoomAdEvent, RoomObjectSamplePlaybackEvent, RoomObjectSoundMachineEvent, RoomObjectStateChangedEvent, RoomObjectTileMouseEvent, RoomObjectWallMouseEvent, RoomObjectWidgetRequestEvent, RoomSpriteMouseEvent } from '@nitrots/events'; import { GetRoomSessionManager, GetSessionDataManager } from '@nitrots/session'; @@ -10,6 +10,7 @@ import { SelectedRoomObjectData } from './utils'; export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomObjectEventManager { + private static readonly CLICK_USER_LOOK_DELAY_MS = 120; private _eventIds: Map> = new Map(); private _selectedAvatarId: number = -1; @@ -17,6 +18,7 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb private _selectedObjectCategory: number = -2; private _whereYouClickIsWhereYouGo: boolean = true; private _objectPlacementSource: string = null; + private _pendingAvatarLookTimeout: ReturnType = null; constructor( private readonly _roomEngine: IRoomEngineServices) @@ -297,7 +299,7 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb } } - private clickRoomObject(event: RoomObjectMouseEvent): void + private clickRoomObject(event: RoomObjectMouseEvent, operation: string): void { if(!event || event.altKey || event.ctrlKey || event.shiftKey) return; @@ -319,20 +321,25 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb return; } + + if((category === RoomObjectCategory.UNIT) && (operation === RoomObjectOperationType.OBJECT_UNDEFINED) && (objectType === RoomObjectUserType.USER)) + { + GetCommunication().connection.send(new ClickUserMessageComposer(objectId)); + } } private handleRoomObjectMouseClickEvent(event: RoomObjectMouseEvent, roomId: number): void { if(!event) return; - this.clickRoomObject(event); - let operation = RoomObjectOperationType.OBJECT_UNDEFINED; const selectedData = this.getSelectedRoomObjectData(roomId); if(selectedData) operation = selectedData.operation; + this.clickRoomObject(event, operation); + let didWalk = false; let didMove = false; @@ -2075,6 +2082,8 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb { if(!this._roomEngine) return; + this.clearPendingAvatarLook(); + const _local_4 = RoomObjectCategory.UNIT; const _local_5 = this._roomEngine.getRoomObject(k, this._selectedAvatarId, _local_4); @@ -2095,15 +2104,26 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb { _local_5.logic.processUpdateMessage(new ObjectAvatarSelectedMessage(true)); - _local_6 = true; + _local_6 = true; - this._selectedAvatarId = _arg_2; + this._selectedAvatarId = _arg_2; - const location = _local_5.getLocation(); + const location = _local_5.getLocation(); - if(location) GetCommunication().connection.send(new RoomUnitLookComposer(~~(location.x), ~~(location.y))); - } - } + if(location) + { + this._pendingAvatarLookTimeout = setTimeout(() => + { + this._pendingAvatarLookTimeout = null; + + if(this.shouldSuppressAvatarLook()) return; + if(this._selectedAvatarId !== _arg_2) return; + + GetCommunication().connection.send(new RoomUnitLookComposer(~~(location.x), ~~(location.y))); + }, RoomObjectEventHandler.CLICK_USER_LOOK_DELAY_MS); + } + } + } const selectionArrow = this._roomEngine.getRoomObjectSelectionArrow(k); @@ -2114,6 +2134,26 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb } } + public clearSelectedAvatar(roomId: number): void + { + this.setSelectedAvatar(roomId, 0, false); + } + + private clearPendingAvatarLook(): void + { + if(!this._pendingAvatarLookTimeout) return; + + clearTimeout(this._pendingAvatarLookTimeout); + this._pendingAvatarLookTimeout = null; + } + + private shouldSuppressAvatarLook(): boolean + { + const control = (globalThis as any).__nitroAvatarClickControl; + + return !!control && (control.suppressRotateUntil > Date.now()); + } + private resetSelectedObjectData(roomId: number): void { if(!this._roomEngine) return; diff --git a/packages/room/src/common/floorplan/FloorplanEditor.ts b/packages/room/src/common/floorplan/FloorplanEditor.ts index d6d2db2..1a5fae8 100644 --- a/packages/room/src/common/floorplan/FloorplanEditor.ts +++ b/packages/room/src/common/floorplan/FloorplanEditor.ts @@ -25,6 +25,8 @@ export class FloorplanEditor private _image: HTMLImageElement; + public onTilemapChange: (() => void) | null = null; + constructor() { const width = TILE_SIZE * MAX_NUM_TILE_PER_AXIS + 20; @@ -297,6 +299,8 @@ export class FloorplanEditor } this.renderSquareSelectionPreview(); + + if(this.onTilemapChange) this.onTilemapChange(); } private renderSquareSelectionPreview(): void @@ -473,6 +477,7 @@ export class FloorplanEditor this._squareSelectStart = null; this._squareSelectEnd = null; this.clearCanvas(); + this.onTilemapChange = null; } diff --git a/packages/session/src/RoomSession.ts b/packages/session/src/RoomSession.ts index bb4beb1..500cc57 100644 --- a/packages/session/src/RoomSession.ts +++ b/packages/session/src/RoomSession.ts @@ -9,6 +9,8 @@ export class RoomSession implements IRoomSession private _roomId: number = 0; private _password: string = null; + private _spawnX: number = -1; + private _spawnY: number = -1; private _state: string = RoomSessionEvent.CREATED; private _tradeMode: number = RoomTradingLevelEnum.NO_TRADING; private _doorMode: number = 0; @@ -57,7 +59,7 @@ export class RoomSession implements IRoomSession { if(!GetCommunication().connection) return false; - GetCommunication().connection.send(new RoomEnterComposer(this._roomId, this._password)); + GetCommunication().connection.send(new RoomEnterComposer(this._roomId, this._password, this._spawnX, this._spawnY)); return true; } @@ -326,6 +328,26 @@ export class RoomSession implements IRoomSession this._password = password; } + public get spawnX(): number + { + return this._spawnX; + } + + public set spawnX(x: number) + { + this._spawnX = x; + } + + public get spawnY(): number + { + return this._spawnY; + } + + public set spawnY(y: number) + { + this._spawnY = y; + } + public get state(): string { return this._state; diff --git a/packages/session/src/RoomSessionManager.ts b/packages/session/src/RoomSessionManager.ts index c8e979d..4e7bf7e 100644 --- a/packages/session/src/RoomSessionManager.ts +++ b/packages/session/src/RoomSessionManager.ts @@ -226,12 +226,10 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList { NitroLogger.log('[RoomSessionManager] Existing session found for room ' + roomId + ' — sending room enter request'); - // Re-send room enter request to the server. This handles two cases: - // 1. Session resume (habbo still in room on server): server treats it - // as a no-op or re-entry to the same room — harmless. - // 2. Server restart (habbo not in any room): server places the habbo - // in the room so the client view matches server state. - GetCommunication().connection.send(new RoomEnterComposer(roomId, password)); + // Re-send room enter request to the server with saved spawn coordinates. + // The server will place the habbo directly at the saved position + // instead of the door tile, providing a seamless reconnection experience. + GetCommunication().connection.send(new RoomEnterComposer(roomId, password, this._savedPosX, this._savedPosY)); // Keep the guard up briefly to absorb any stray server-side redirects // (DesktopViewEvent, etc.) from the login packet sequence, then drop it. @@ -256,9 +254,9 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._sessions.clear(); this._viewerSession = null; - // Send the room enter request. The guard stays active to block - // DesktopViewEvent / home room redirects from the server's login sequence. - this.createSession(roomId, password); + // Send the room enter request with saved spawn coordinates. The server + // will place the habbo at the saved position instead of the door tile. + this.createSession(roomId, password, this._savedPosX, this._savedPosY); // Keep the guard up for a generous window to absorb any DesktopViewEvent // or other server-side redirects that arrive after authentication. @@ -299,13 +297,32 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList const password = sessionStorage.getItem(STORAGE_KEY_ROOM_PASSWORD) || null; - NitroLogger.log('[RoomSessionManager] Restoring session for room ' + roomId + ' from sessionStorage'); + // Read saved position for page-reload restore + let spawnX = -1; + let spawnY = -1; + + try + { + const posX = sessionStorage.getItem(STORAGE_KEY_POS_X); + const posY = sessionStorage.getItem(STORAGE_KEY_POS_Y); + + if(posX && posY) + { + spawnX = parseInt(posX, 10); + spawnY = parseInt(posY, 10); + + if(isNaN(spawnX) || isNaN(spawnY)) { spawnX = -1; spawnY = -1; } + } + } + catch(e) { /* ignore */ } + + NitroLogger.log('[RoomSessionManager] Restoring session for room ' + roomId + ' from sessionStorage (spawn: ' + spawnX + ', ' + spawnY + ')'); // Set the guard so DesktopViewEvent from the server's login sequence // doesn't kick us to hotel view before we enter the room this._isReconnecting = true; - this.createSession(roomId, password); + this.createSession(roomId, password, spawnX, spawnY); // Drop the guard when room entry succeeds or after timeout this.clearGuardTimer(); @@ -364,7 +381,7 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList sessionStorage.removeItem(STORAGE_KEY_ROOM_PASSWORD); // Note: position keys (POS_X, POS_Y) are NOT cleared here. // They persist across the disconnect→reconnect cycle and are - // consumed by walkToSavedPosition() after successful re-entry. + // sent to the server as spawn coordinates during re-entry. } catch(e) { @@ -378,7 +395,6 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList { sessionStorage.removeItem(STORAGE_KEY_POS_X); sessionStorage.removeItem(STORAGE_KEY_POS_Y); - sessionStorage.removeItem('nitro.session.posLocked'); } catch(e) { @@ -398,9 +414,6 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._savedPosX = parseInt(posX, 10); this._savedPosY = parseInt(posY, 10); - // Lock position saving so room re-entry doesn't overwrite saved position - sessionStorage.setItem('nitro.session.posLocked', '1'); - NitroLogger.log('[RoomSessionManager] Snapshot saved position (' + this._savedPosX + ', ' + this._savedPosY + ')'); } catch(e) @@ -410,24 +423,6 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList } } - private walkToSavedPosition(): void - { - const x = this._savedPosX; - const y = this._savedPosY; - - // Reset after use - this._savedPosX = -1; - this._savedPosY = -1; - - // Unlock position saving so normal movement is tracked again - try { sessionStorage.removeItem('nitro.session.posLocked'); } catch(e) { /* ignore */ } - - if(x < 0 || y < 0 || isNaN(x) || isNaN(y)) return; - - NitroLogger.log('[RoomSessionManager] Walking to saved position (' + x + ', ' + y + ')'); - GetCommunication().connection.send(new RoomUnitWalkComposer(x, y)); - } - private setHandlers(session: IRoomSession): void { if(!this._handlers || !this._handlers.length) return; @@ -458,12 +453,14 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList return existing; } - public createSession(roomId: number, password: string = null): boolean + public createSession(roomId: number, password: string = null, spawnX: number = -1, spawnY: number = -1): boolean { const session = new RoomSession(); session.roomId = roomId; session.password = password; + session.spawnX = spawnX; + session.spawnY = spawnY; return this.addSession(session); } @@ -579,9 +576,16 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList { NitroLogger.log('[RoomSessionManager] Room ready confirmed - dropping guard in 3s'); - // Walk to the saved position (where the user was before disconnect). - // Delay briefly so the server finishes placing the avatar in the room. - setTimeout(() => this.walkToSavedPosition(), 1000); + // If we have saved spawn coordinates, send a walk command so the + // avatar moves to their previous position. This handles the EMU-restart + // case where the server has no ghost session and spawns at the door. + if(this._savedPosX >= 0 && this._savedPosY >= 0) + { + NitroLogger.log('[RoomSessionManager] Walking to saved position (' + this._savedPosX + ', ' + this._savedPosY + ')'); + GetCommunication().connection.send(new RoomUnitWalkComposer(this._savedPosX, this._savedPosY)); + this._savedPosX = -1; + this._savedPosY = -1; + } this.clearGuardTimer(); this._reconnectGuardTimer = setTimeout(() => diff --git a/packages/session/src/handler/RoomChatHandler.ts b/packages/session/src/handler/RoomChatHandler.ts index aa717f3..e515ae5 100644 --- a/packages/session/src/handler/RoomChatHandler.ts +++ b/packages/session/src/handler/RoomChatHandler.ts @@ -54,7 +54,7 @@ export class RoomChatHandler extends BaseHandler if(!parser) return; - GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, parser.giverUserId, '', RoomSessionChatEvent.CHAT_TYPE_HAND_ITEM_RECEIVED, SystemChatStyleEnum.GENERIC, [], parser.handItemType)); + GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, parser.giverUserId, '', RoomSessionChatEvent.CHAT_TYPE_HAND_ITEM_RECEIVED, SystemChatStyleEnum.GENERIC, [], null, parser.handItemType)); } private onRespectReceivedEvent(event: RespectReceivedEvent): void @@ -136,7 +136,7 @@ export class RoomChatHandler extends BaseHandler break; } - GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, petData.roomIndex, '', chatType, SystemChatStyleEnum.GENERIC, null, userRoomIndex)); + GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, petData.roomIndex, '', chatType, SystemChatStyleEnum.GENERIC, [], null, userRoomIndex)); } private onFloodControlEvent(event: FloodControlEvent): void @@ -168,6 +168,6 @@ export class RoomChatHandler extends BaseHandler if(!parser) return; - GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, session.ownRoomIndex, '', RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING, SystemChatStyleEnum.GENERIC, [], parser.seconds)); + GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, session.ownRoomIndex, '', RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING, SystemChatStyleEnum.GENERIC, [], null, parser.seconds)); } } diff --git a/packages/utils/src/filters/WiredFilter.ts b/packages/utils/src/filters/WiredFilter.ts index 303ae86..10bc8d0 100644 --- a/packages/utils/src/filters/WiredFilter.ts +++ b/packages/utils/src/filters/WiredFilter.ts @@ -63,13 +63,14 @@ export class WiredFilter extends Filter void main(void) { vec4 currentColor = texture(uTexture, vTextureCoord); - vec3 colorLine = uLineColor * currentColor.a; - vec3 colorOverlay = uColor * currentColor.a; + vec3 colorLine = uLineColor; + vec3 colorOverlay = uColor; if(currentColor.r == 0.0 && currentColor.g == 0.0 && currentColor.b == 0.0 && currentColor.a > 0.0) { finalColor = vec4(colorLine.r, colorLine.g, colorLine.b, currentColor.a); } else if(currentColor.a > 0.0) { - finalColor = vec4(colorOverlay.r, colorOverlay.g, colorOverlay.b, currentColor.a); + vec3 blendedOverlay = mix(currentColor.rgb, colorOverlay, 0.28); + finalColor = vec4(blendedOverlay.r, blendedOverlay.g, blendedOverlay.b, currentColor.a); } } `,