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/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(() =>