🆙 Stage 2 reconnect

This commit is contained in:
duckietm
2026-03-20 17:10:56 +01:00
parent 958693fe2e
commit 4e41e9db5c
3 changed files with 68 additions and 47 deletions
+3 -8
View File
@@ -737,18 +737,13 @@ export class RoomMessageHandler
this._roomEngine.updateRoomObjectUserLocation(this._currentRoomId, status.id, location, goal, status.canStandUp, height, direction, status.headDirection); this._roomEngine.updateRoomObjectUserLocation(this._currentRoomId, status.id, location, goal, status.canStandUp, height, direction, status.headDirection);
this._roomEngine.updateRoomObjectUserFlatControl(this._currentRoomId, status.id, null); 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) if(status.id === this._ownRoomIndex)
{ {
try try
{ {
const locked = sessionStorage.getItem('nitro.session.posLocked'); sessionStorage.setItem('nitro.session.lastPosX', status.x.toString());
sessionStorage.setItem('nitro.session.lastPosY', status.y.toString());
if(!locked)
{
sessionStorage.setItem('nitro.session.lastPosX', status.x.toString());
sessionStorage.setItem('nitro.session.lastPosY', status.y.toString());
}
} }
catch(e) { /* ignore */ } catch(e) { /* ignore */ }
} }
+23 -1
View File
@@ -9,6 +9,8 @@ export class RoomSession implements IRoomSession
private _roomId: number = 0; private _roomId: number = 0;
private _password: string = null; private _password: string = null;
private _spawnX: number = -1;
private _spawnY: number = -1;
private _state: string = RoomSessionEvent.CREATED; private _state: string = RoomSessionEvent.CREATED;
private _tradeMode: number = RoomTradingLevelEnum.NO_TRADING; private _tradeMode: number = RoomTradingLevelEnum.NO_TRADING;
private _doorMode: number = 0; private _doorMode: number = 0;
@@ -57,7 +59,7 @@ export class RoomSession implements IRoomSession
{ {
if(!GetCommunication().connection) return false; 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; return true;
} }
@@ -326,6 +328,26 @@ export class RoomSession implements IRoomSession
this._password = password; 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 public get state(): string
{ {
return this._state; return this._state;
+42 -38
View File
@@ -226,12 +226,10 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
{ {
NitroLogger.log('[RoomSessionManager] Existing session found for room ' + roomId + ' — sending room enter request'); 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: // Re-send room enter request to the server with saved spawn coordinates.
// 1. Session resume (habbo still in room on server): server treats it // The server will place the habbo directly at the saved position
// as a no-op or re-entry to the same room — harmless. // instead of the door tile, providing a seamless reconnection experience.
// 2. Server restart (habbo not in any room): server places the habbo GetCommunication().connection.send(new RoomEnterComposer(roomId, password, this._savedPosX, this._savedPosY));
// in the room so the client view matches server state.
GetCommunication().connection.send(new RoomEnterComposer(roomId, password));
// Keep the guard up briefly to absorb any stray server-side redirects // Keep the guard up briefly to absorb any stray server-side redirects
// (DesktopViewEvent, etc.) from the login packet sequence, then drop it. // (DesktopViewEvent, etc.) from the login packet sequence, then drop it.
@@ -256,9 +254,9 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
this._sessions.clear(); this._sessions.clear();
this._viewerSession = null; this._viewerSession = null;
// Send the room enter request. The guard stays active to block // Send the room enter request with saved spawn coordinates. The server
// DesktopViewEvent / home room redirects from the server's login sequence. // will place the habbo at the saved position instead of the door tile.
this.createSession(roomId, password); this.createSession(roomId, password, this._savedPosX, this._savedPosY);
// Keep the guard up for a generous window to absorb any DesktopViewEvent // Keep the guard up for a generous window to absorb any DesktopViewEvent
// or other server-side redirects that arrive after authentication. // 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; 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 // Set the guard so DesktopViewEvent from the server's login sequence
// doesn't kick us to hotel view before we enter the room // doesn't kick us to hotel view before we enter the room
this._isReconnecting = true; this._isReconnecting = true;
this.createSession(roomId, password); this.createSession(roomId, password, spawnX, spawnY);
// Drop the guard when room entry succeeds or after timeout // Drop the guard when room entry succeeds or after timeout
this.clearGuardTimer(); this.clearGuardTimer();
@@ -364,7 +381,7 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
sessionStorage.removeItem(STORAGE_KEY_ROOM_PASSWORD); sessionStorage.removeItem(STORAGE_KEY_ROOM_PASSWORD);
// Note: position keys (POS_X, POS_Y) are NOT cleared here. // Note: position keys (POS_X, POS_Y) are NOT cleared here.
// They persist across the disconnect→reconnect cycle and are // 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) catch(e)
{ {
@@ -378,7 +395,6 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
{ {
sessionStorage.removeItem(STORAGE_KEY_POS_X); sessionStorage.removeItem(STORAGE_KEY_POS_X);
sessionStorage.removeItem(STORAGE_KEY_POS_Y); sessionStorage.removeItem(STORAGE_KEY_POS_Y);
sessionStorage.removeItem('nitro.session.posLocked');
} }
catch(e) catch(e)
{ {
@@ -398,9 +414,6 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
this._savedPosX = parseInt(posX, 10); this._savedPosX = parseInt(posX, 10);
this._savedPosY = parseInt(posY, 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 + ')'); NitroLogger.log('[RoomSessionManager] Snapshot saved position (' + this._savedPosX + ', ' + this._savedPosY + ')');
} }
catch(e) 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 private setHandlers(session: IRoomSession): void
{ {
if(!this._handlers || !this._handlers.length) return; if(!this._handlers || !this._handlers.length) return;
@@ -458,12 +453,14 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
return existing; 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(); const session = new RoomSession();
session.roomId = roomId; session.roomId = roomId;
session.password = password; session.password = password;
session.spawnX = spawnX;
session.spawnY = spawnY;
return this.addSession(session); return this.addSession(session);
} }
@@ -579,9 +576,16 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList
{ {
NitroLogger.log('[RoomSessionManager] Room ready confirmed - dropping guard in 3s'); NitroLogger.log('[RoomSessionManager] Room ready confirmed - dropping guard in 3s');
// Walk to the saved position (where the user was before disconnect). // If we have saved spawn coordinates, send a walk command so the
// Delay briefly so the server finishes placing the avatar in the room. // avatar moves to their previous position. This handles the EMU-restart
setTimeout(() => this.walkToSavedPosition(), 1000); // 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.clearGuardTimer();
this._reconnectGuardTimer = setTimeout(() => this._reconnectGuardTimer = setTimeout(() =>