You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 07:26:18 +00:00
🆙 Stage 2 reconnect
This commit is contained in:
@@ -8,6 +8,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a grace period for disconnected users. Instead of immediately
|
||||||
|
* disposing a Habbo when their WebSocket drops, the Habbo is held in
|
||||||
|
* a "ghost" state for a configurable number of seconds. If the same
|
||||||
|
* user reconnects (via SSO ticket) within the grace window, their
|
||||||
|
* existing Habbo object is resumed on the new connection — keeping
|
||||||
|
* them in their room, preserving inventory state, etc.
|
||||||
|
*
|
||||||
|
* Config key: session.reconnect.grace.seconds (default: 30)
|
||||||
|
*/
|
||||||
public class SessionResumeManager {
|
public class SessionResumeManager {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class);
|
||||||
@@ -27,6 +37,12 @@ public class SessionResumeManager {
|
|||||||
return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30);
|
return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Park a disconnected Habbo in ghost mode. Their room presence is
|
||||||
|
* preserved, but the old GameClient channel is closed.
|
||||||
|
*
|
||||||
|
* @return true if the habbo was parked (grace period > 0), false if immediate dispose should happen
|
||||||
|
*/
|
||||||
public boolean parkHabbo(Habbo habbo, String ssoTicket) {
|
public boolean parkHabbo(Habbo habbo, String ssoTicket) {
|
||||||
int graceSeconds = getGracePeriodSeconds();
|
int graceSeconds = getGracePeriodSeconds();
|
||||||
if (graceSeconds <= 0) {
|
if (graceSeconds <= 0) {
|
||||||
@@ -35,6 +51,7 @@ public class SessionResumeManager {
|
|||||||
|
|
||||||
int userId = habbo.getHabboInfo().getId();
|
int userId = habbo.getHabboInfo().getId();
|
||||||
|
|
||||||
|
// Cancel any existing ghost session for this user
|
||||||
GhostSession existing = ghostSessions.remove(userId);
|
GhostSession existing = ghostSessions.remove(userId);
|
||||||
if (existing != null && existing.disposeFuture != null) {
|
if (existing != null && existing.disposeFuture != null) {
|
||||||
existing.disposeFuture.cancel(false);
|
existing.disposeFuture.cancel(false);
|
||||||
@@ -43,10 +60,12 @@ public class SessionResumeManager {
|
|||||||
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
|
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
|
||||||
habbo.getHabboInfo().getUsername(), userId, graceSeconds);
|
habbo.getHabboInfo().getUsername(), userId, graceSeconds);
|
||||||
|
|
||||||
|
// Restore the SSO ticket so the client can reconnect with the same ticket
|
||||||
if (ssoTicket != null && !ssoTicket.isEmpty()) {
|
if (ssoTicket != null && !ssoTicket.isEmpty()) {
|
||||||
restoreSsoTicket(userId, ssoTicket);
|
restoreSsoTicket(userId, ssoTicket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule the final disconnect after the grace period
|
||||||
ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
|
ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
|
||||||
GhostSession ghost = ghostSessions.remove(userId);
|
GhostSession ghost = ghostSessions.remove(userId);
|
||||||
if (ghost != null) {
|
if (ghost != null) {
|
||||||
@@ -60,12 +79,18 @@ public class SessionResumeManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to resume a ghost session for the given user ID.
|
||||||
|
*
|
||||||
|
* @return the parked Habbo if found within grace period, null otherwise
|
||||||
|
*/
|
||||||
public Habbo resumeSession(int userId) {
|
public Habbo resumeSession(int userId) {
|
||||||
GhostSession ghost = ghostSessions.remove(userId);
|
GhostSession ghost = ghostSessions.remove(userId);
|
||||||
if (ghost == null) {
|
if (ghost == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancel the scheduled dispose
|
||||||
if (ghost.disposeFuture != null) {
|
if (ghost.disposeFuture != null) {
|
||||||
ghost.disposeFuture.cancel(false);
|
ghost.disposeFuture.cancel(false);
|
||||||
}
|
}
|
||||||
@@ -76,10 +101,16 @@ public class SessionResumeManager {
|
|||||||
return ghost.habbo;
|
return ghost.habbo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user has a ghost session (is in grace period).
|
||||||
|
*/
|
||||||
public boolean hasGhostSession(int userId) {
|
public boolean hasGhostSession(int userId) {
|
||||||
return ghostSessions.containsKey(userId);
|
return ghostSessions.containsKey(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately expire all ghost sessions (e.g. on emulator shutdown).
|
||||||
|
*/
|
||||||
public void disposeAll() {
|
public void disposeAll() {
|
||||||
for (GhostSession ghost : ghostSessions.values()) {
|
for (GhostSession ghost : ghostSessions.values()) {
|
||||||
if (ghost.disposeFuture != null) {
|
if (ghost.disposeFuture != null) {
|
||||||
@@ -90,6 +121,9 @@ public class SessionResumeManager {
|
|||||||
ghostSessions.clear();
|
ghostSessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the actual full disconnect that normally happens in Habbo.disconnect().
|
||||||
|
*/
|
||||||
private void performFullDisconnect(Habbo habbo) {
|
private void performFullDisconnect(Habbo habbo) {
|
||||||
try {
|
try {
|
||||||
habbo.getHabboInfo().setOnline(false);
|
habbo.getHabboInfo().setOnline(false);
|
||||||
@@ -97,6 +131,8 @@ public class SessionResumeManager {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
|
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the SSO ticket now that the grace period is truly over
|
||||||
clearSsoTicket(habbo.getHabboInfo().getId());
|
clearSsoTicket(habbo.getHabboInfo().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ public class RoomManager {
|
|||||||
/**
|
/**
|
||||||
* Loads a room, optionally loading its data.
|
* Loads a room, optionally loading its data.
|
||||||
* If the room is already being loaded in the background, this will wait for that to complete.
|
* If the room is already being loaded in the background, this will wait for that to complete.
|
||||||
*
|
*
|
||||||
* @param id The room ID
|
* @param id The room ID
|
||||||
* @param loadData Whether to load room data (items, bots, pets, etc.)
|
* @param loadData Whether to load room data (items, bots, pets, etc.)
|
||||||
* @return The loaded room, or null if not found
|
* @return The loaded room, or null if not found
|
||||||
@@ -499,14 +499,18 @@ public class RoomManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void enterRoom(Habbo habbo, int roomId, String password) {
|
public void enterRoom(Habbo habbo, int roomId, String password) {
|
||||||
this.enterRoom(habbo, roomId, password, false, null);
|
this.enterRoom(habbo, roomId, password, false, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks) {
|
public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks) {
|
||||||
this.enterRoom(habbo, roomId, password, overrideChecks, null);
|
this.enterRoom(habbo, roomId, password, overrideChecks, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks, RoomTile doorLocation) {
|
public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks, RoomTile doorLocation) {
|
||||||
|
this.enterRoom(habbo, roomId, password, overrideChecks, doorLocation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks, RoomTile doorLocation, boolean isReconnectSpawn) {
|
||||||
Room room = this.loadRoom(roomId, true);
|
Room room = this.loadRoom(roomId, true);
|
||||||
|
|
||||||
if (room == null)
|
if (room == null)
|
||||||
@@ -547,7 +551,7 @@ public class RoomManager {
|
|||||||
room.hasRights(habbo) ||
|
room.hasRights(habbo) ||
|
||||||
(room.getState().equals(RoomState.INVISIBLE) && room.hasRights(habbo)) ||
|
(room.getState().equals(RoomState.INVISIBLE) && room.hasRights(habbo)) ||
|
||||||
(room.hasGuild() && room.getGuildRightLevel(habbo).isGreaterThan(RoomRightLevels.GUILD_RIGHTS))) {
|
(room.hasGuild() && room.getGuildRightLevel(habbo).isGreaterThan(RoomRightLevels.GUILD_RIGHTS))) {
|
||||||
this.openRoom(habbo, room, doorLocation);
|
this.openRoom(habbo, room, doorLocation, isReconnectSpawn);
|
||||||
} else if (room.getState() == RoomState.LOCKED) {
|
} else if (room.getState() == RoomState.LOCKED) {
|
||||||
boolean rightsFound = false;
|
boolean rightsFound = false;
|
||||||
|
|
||||||
@@ -572,7 +576,7 @@ public class RoomManager {
|
|||||||
room.addToQueue(habbo);
|
room.addToQueue(habbo);
|
||||||
} else if (room.getState() == RoomState.PASSWORD) {
|
} else if (room.getState() == RoomState.PASSWORD) {
|
||||||
if (room.getPassword().equalsIgnoreCase(password))
|
if (room.getPassword().equalsIgnoreCase(password))
|
||||||
this.openRoom(habbo, room, doorLocation);
|
this.openRoom(habbo, room, doorLocation, isReconnectSpawn);
|
||||||
else {
|
else {
|
||||||
habbo.getClient().sendResponse(new GenericErrorMessagesComposer(GenericErrorMessagesComposer.WRONG_PASSWORD_USED));
|
habbo.getClient().sendResponse(new GenericErrorMessagesComposer(GenericErrorMessagesComposer.WRONG_PASSWORD_USED));
|
||||||
habbo.getClient().sendResponse(new HotelViewComposer());
|
habbo.getClient().sendResponse(new HotelViewComposer());
|
||||||
@@ -585,6 +589,10 @@ public class RoomManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void openRoom(Habbo habbo, Room room, RoomTile doorLocation) {
|
void openRoom(Habbo habbo, Room room, RoomTile doorLocation) {
|
||||||
|
this.openRoom(habbo, room, doorLocation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openRoom(Habbo habbo, Room room, RoomTile doorLocation, boolean isReconnectSpawn) {
|
||||||
if (room == null || room.getLayout() == null)
|
if (room == null || room.getLayout() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -623,7 +631,13 @@ public class RoomManager {
|
|||||||
if (doorLocation == null) {
|
if (doorLocation == null) {
|
||||||
habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]);
|
habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]);
|
||||||
habbo.getRoomUnit().setHeadRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]);
|
habbo.getRoomUnit().setHeadRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]);
|
||||||
|
} else if (isReconnectSpawn) {
|
||||||
|
// Reconnect spawn: place at tile but keep normal room behavior
|
||||||
|
// (user can still leave by door, no teleport flags)
|
||||||
|
habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]);
|
||||||
|
habbo.getRoomUnit().setHeadRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]);
|
||||||
} else {
|
} else {
|
||||||
|
// Furniture teleport spawn
|
||||||
habbo.getRoomUnit().setCanLeaveRoomByDoor(false);
|
habbo.getRoomUnit().setCanLeaveRoomByDoor(false);
|
||||||
habbo.getRoomUnit().isTeleporting = true;
|
habbo.getRoomUnit().isTeleporting = true;
|
||||||
HabboItem topItem = room.getTopItemAt(doorLocation.x, doorLocation.y);
|
HabboItem topItem = room.getTopItemAt(doorLocation.x, doorLocation.y);
|
||||||
|
|||||||
+20
-1
@@ -1,9 +1,9 @@
|
|||||||
package com.eu.habbo.messages.incoming.handshake;
|
package com.eu.habbo.messages.incoming.handshake;
|
||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.messenger.Messenger;
|
||||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||||
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
|
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
|
||||||
import com.eu.habbo.habbohotel.messenger.Messenger;
|
|
||||||
import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem;
|
import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem;
|
||||||
import com.eu.habbo.habbohotel.modtool.ModToolSanctions;
|
import com.eu.habbo.habbohotel.modtool.ModToolSanctions;
|
||||||
import com.eu.habbo.habbohotel.navigation.NavigatorSavedSearch;
|
import com.eu.habbo.habbohotel.navigation.NavigatorSavedSearch;
|
||||||
@@ -84,14 +84,21 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.client.getHabbo() == null) {
|
if (this.client.getHabbo() == null) {
|
||||||
|
// Store SSO ticket on client for grace period tracking
|
||||||
this.client.setSsoTicket(sso);
|
this.client.setSsoTicket(sso);
|
||||||
|
|
||||||
|
// Race condition fix: if the old WebSocket connection is still alive on the
|
||||||
|
// server when the client reconnects, the SSO ticket won't be in the DB yet
|
||||||
|
// (it was cleared on first login, and parkHabbo hasn't run because the old
|
||||||
|
// channel hasn't closed). Find the old client by SSO ticket and force-dispose
|
||||||
|
// it, which parks the habbo and restores the ticket to the DB.
|
||||||
GameClient existingClient = Emulator.getGameServer().getGameClientManager().findClientBySsoTicket(sso);
|
GameClient existingClient = Emulator.getGameServer().getGameClientManager().findClientBySsoTicket(sso);
|
||||||
if (existingClient != null && existingClient != this.client) {
|
if (existingClient != null && existingClient != this.client) {
|
||||||
LOGGER.info("[SessionResume] Found existing client with same SSO ticket — disposing old connection to trigger parking");
|
LOGGER.info("[SessionResume] Found existing client with same SSO ticket — disposing old connection to trigger parking");
|
||||||
Emulator.getGameServer().getGameClientManager().disposeClient(existingClient);
|
Emulator.getGameServer().getGameClientManager().disposeClient(existingClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First, look up the user ID to check for ghost sessions
|
||||||
int lookupUserId = 0;
|
int lookupUserId = 0;
|
||||||
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) {
|
java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) {
|
||||||
@@ -105,6 +112,7 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
LOGGER.error("Caught exception looking up user for session resume", e);
|
LOGGER.error("Caught exception looking up user for session resume", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this user has a ghost session (disconnected within grace period)
|
||||||
Habbo habbo = null;
|
Habbo habbo = null;
|
||||||
boolean isSessionResume = false;
|
boolean isSessionResume = false;
|
||||||
|
|
||||||
@@ -113,6 +121,7 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (habbo != null) {
|
if (habbo != null) {
|
||||||
|
// Session resume — reattach the existing Habbo to the new client
|
||||||
isSessionResume = true;
|
isSessionResume = true;
|
||||||
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
|
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
|
||||||
habbo.getHabboInfo().getUsername(), habbo.getHabboInfo().getId());
|
habbo.getHabboInfo().getUsername(), habbo.getHabboInfo().getId());
|
||||||
@@ -121,6 +130,7 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
this.client.setHabbo(habbo);
|
this.client.setHabbo(habbo);
|
||||||
this.client.setMachineId(habbo.getHabboInfo().getMachineID());
|
this.client.setMachineId(habbo.getHabboInfo().getMachineID());
|
||||||
|
|
||||||
|
// Clear the SSO ticket now that session is resumed (prevent reuse)
|
||||||
if (!Emulator.debugging) {
|
if (!Emulator.debugging) {
|
||||||
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
||||||
@@ -132,6 +142,7 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Normal login — load from database
|
||||||
habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
|
habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +188,11 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
int roomIdToEnter = 0;
|
int roomIdToEnter = 0;
|
||||||
|
|
||||||
if (isSessionResume) {
|
if (isSessionResume) {
|
||||||
|
// On session resume, DON'T set roomIdToEnter. The client keeps its
|
||||||
|
// existing room view alive and the habbo is already in the room on
|
||||||
|
// the server. Setting roomIdToEnter = 0 prevents UserHomeRoomComposer
|
||||||
|
// from triggering a full room re-entry on the client (which would
|
||||||
|
// tear down and rebuild the room view).
|
||||||
Room currentRoom = habbo.getHabboInfo().getCurrentRoom();
|
Room currentRoom = habbo.getHabboInfo().getCurrentRoom();
|
||||||
if (currentRoom != null) {
|
if (currentRoom != null) {
|
||||||
LOGGER.info("[SessionResume] {} is still in room {} — client will resume in-place",
|
LOGGER.info("[SessionResume] {} is still in room {} — client will resume in-place",
|
||||||
@@ -213,6 +229,8 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
|
|
||||||
this.client.sendResponses(messages);
|
this.client.sendResponses(messages);
|
||||||
|
|
||||||
|
//Hardcoded
|
||||||
|
//this.client.sendResponse(new ForumsTestComposer());
|
||||||
this.client.sendResponse(new InventoryAchievementsComposer());
|
this.client.sendResponse(new InventoryAchievementsComposer());
|
||||||
|
|
||||||
ModToolSanctions modToolSanctions = Emulator.getGameEnvironment().getModToolSanctions();
|
ModToolSanctions modToolSanctions = Emulator.getGameEnvironment().getModToolSanctions();
|
||||||
@@ -248,6 +266,7 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip login-only events on session resume (welcome alerts, login events, etc.)
|
||||||
if (!isSessionResume) {
|
if (!isSessionResume) {
|
||||||
UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin());
|
UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin());
|
||||||
Emulator.getPluginManager().fireEvent(userLoginEvent);
|
Emulator.getPluginManager().fireEvent(userLoginEvent);
|
||||||
|
|||||||
+56
-3
@@ -2,18 +2,38 @@ package com.eu.habbo.messages.incoming.rooms;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.rooms.Room;
|
import com.eu.habbo.habbohotel.rooms.Room;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class RequestRoomLoadEvent extends MessageHandler {
|
public class RequestRoomLoadEvent extends MessageHandler {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RequestRoomLoadEvent.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle() throws Exception {
|
public void handle() throws Exception {
|
||||||
int roomId = this.packet.readInt();
|
int roomId = this.packet.readInt();
|
||||||
String password = this.packet.readString();
|
String password = this.packet.readString();
|
||||||
|
|
||||||
|
// Optional spawn coordinates from the client (for future reconnection support).
|
||||||
|
int spawnX = -1;
|
||||||
|
int spawnY = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
int remaining = this.packet.getBuffer().readableBytes();
|
||||||
|
if (remaining >= 8) {
|
||||||
|
spawnX = this.packet.readInt();
|
||||||
|
spawnY = this.packet.readInt();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
spawnX = -1;
|
||||||
|
spawnY = -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset stale loadingRoom if timestamp has expired (indicates failed/stuck load)
|
// Reset stale loadingRoom if timestamp has expired (indicates failed/stuck load)
|
||||||
if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0
|
if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0
|
||||||
&& this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) {
|
&& this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) {
|
||||||
this.client.getHabbo().getHabboInfo().setLoadingRoom(0);
|
this.client.getHabbo().getHabboInfo().setLoadingRoom(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +50,18 @@ public class RequestRoomLoadEvent extends MessageHandler {
|
|||||||
|
|
||||||
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
|
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
|
||||||
if (room != null) {
|
if (room != null) {
|
||||||
|
// If re-entering the same room (session resume / reconnect), capture
|
||||||
|
// the user's current position before removal so we can respawn there.
|
||||||
|
if (room.getId() == roomId && spawnX < 0 && spawnY < 0
|
||||||
|
&& this.client.getHabbo().getRoomUnit() != null
|
||||||
|
&& this.client.getHabbo().getRoomUnit().getCurrentLocation() != null) {
|
||||||
|
RoomTile currentLoc = this.client.getHabbo().getRoomUnit().getCurrentLocation();
|
||||||
|
spawnX = currentLoc.x;
|
||||||
|
spawnY = currentLoc.y;
|
||||||
|
LOGGER.info("[RequestRoomLoadEvent] Re-entering same room {} — preserving position ({}, {})",
|
||||||
|
roomId, spawnX, spawnY);
|
||||||
|
}
|
||||||
|
|
||||||
Emulator.getGameEnvironment().getRoomManager().logExit(this.client.getHabbo());
|
Emulator.getGameEnvironment().getRoomManager().logExit(this.client.getHabbo());
|
||||||
|
|
||||||
room.removeHabbo(this.client.getHabbo(), true);
|
room.removeHabbo(this.client.getHabbo(), true);
|
||||||
@@ -41,7 +73,28 @@ public class RequestRoomLoadEvent extends MessageHandler {
|
|||||||
this.client.getHabbo().getRoomUnit().isTeleporting = false;
|
this.client.getHabbo().getRoomUnit().isTeleporting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), roomId, password);
|
// Resolve spawn tile from coordinates (either from client or from saved position above)
|
||||||
|
RoomTile spawnTile = null;
|
||||||
|
|
||||||
|
if (spawnX >= 0 && spawnY >= 0) {
|
||||||
|
Room targetRoom = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
|
||||||
|
if (targetRoom == null) {
|
||||||
|
targetRoom = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId);
|
||||||
|
}
|
||||||
|
if (targetRoom != null && targetRoom.getLayout() != null) {
|
||||||
|
RoomTile tile = targetRoom.getLayout().getTile((short) spawnX, (short) spawnY);
|
||||||
|
if (tile != null && tile.isWalkable()) {
|
||||||
|
spawnTile = tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isReconnect = spawnTile != null;
|
||||||
|
LOGGER.debug("[RequestRoomLoadEvent] Entering room {} (spawnTile={}, isReconnect={})",
|
||||||
|
roomId,
|
||||||
|
spawnTile != null ? "(" + spawnTile.x + "," + spawnTile.y + ")" : "door",
|
||||||
|
isReconnect);
|
||||||
|
Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), roomId, password, false, spawnTile, isReconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user