Merge remote-tracking branch 'upstream/main'

# Conflicts:
#	Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasHandItem.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToFurni.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserToFurni.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksTile.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksUser.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredConditionType.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredTriggerType.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java
#	Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ClickFurniEvent.java
#	Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/ClickUserEvent.java
This commit is contained in:
Lorenzune
2026-03-21 14:46:11 +01:00
49 changed files with 1613 additions and 92 deletions
@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS `user_prefixes` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`text` VARCHAR(50) NOT NULL,
`color` VARCHAR(255) NOT NULL DEFAULT '#FFFFFF',
`icon` VARCHAR(50) NOT NULL DEFAULT '',
`effect` VARCHAR(50) NOT NULL DEFAULT '',
`active` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_user_active` (`user_id`, `active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,115 @@
-- ============================================================
-- Custom Prefix System - Complete Setup
-- ============================================================
-- 1. Main user prefixes table
CREATE TABLE IF NOT EXISTS `user_prefixes` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`text` VARCHAR(50) NOT NULL,
`color` VARCHAR(255) NOT NULL DEFAULT '#FFFFFF',
`icon` VARCHAR(50) NOT NULL DEFAULT '',
`effect` VARCHAR(50) NOT NULL DEFAULT '',
`active` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_user_active` (`user_id`, `active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2. Prefix settings table
CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
`key_name` VARCHAR(100) NOT NULL,
`value` VARCHAR(255) NOT NULL,
PRIMARY KEY (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default settings
INSERT IGNORE INTO `custom_prefix_settings` (`key_name`, `value`) VALUES
('max_length', '15'),
('min_rank_to_buy', '1'),
('price_credits', '5'),
('price_points', '0'),
('points_type', '0');
-- 3. Blacklisted words table
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`word` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Example blacklist entries (customize as needed)
INSERT IGNORE INTO `custom_prefix_blacklist` (`word`) VALUES
('admin'),
('staff'),
('mod'),
('owner');
-- 4. Add effect column (if table already exists without it)
-- ALTER TABLE `user_prefixes` ADD COLUMN IF NOT EXISTS `effect` VARCHAR(50) NOT NULL DEFAULT '' AFTER `icon`;
-- ============================================================
-- Catalog page for custom prefixes
-- ============================================================
-- NOTE: Adjust parent_id to match your catalog parent category ID.
-- Example: parent_id = -1 for root, or the ID of your "Extra" / "Specials" category
INSERT INTO `catalog_pages` (
`parent_id`, `caption`, `caption_save`, `icon_image`, `visible`, `enabled`,
`min_rank`, `page_layout`, `page_strings_1`, `page_strings_2`
) VALUES (
-1,
'Custom Prefix',
'custom_prefix',
1,
1,
1,
1,
'custom_prefix',
'Create your own custom prefix!\rChoose text, colors, icon and effects to stand out in chat.',
''
);
-- ============================================================
-- Command texts (insert into emulator_texts if not present)
-- ============================================================
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
-- GivePrefix command
('commands.keys.cmd_give_prefix', 'giveprefix'),
('commands.error.cmd_give_prefix.usage', 'Usage: :giveprefix <username> <text> <color> [icon] [effect]'),
('commands.error.cmd_give_prefix.invalid_color', 'Invalid color format. Use hex format (#FF0000).'),
('commands.error.cmd_give_prefix.too_long', 'Prefix text is too long (max 15 characters).'),
('commands.error.cmd_give_prefix.user_not_found', 'User not found or not online.'),
('commands.succes.cmd_give_prefix', 'Prefix {%prefix%} successfully given to %user%!'),
-- ListPrefixes command
('commands.keys.cmd_list_prefixes', 'listprefixes'),
('commands.error.cmd_list_prefixes.usage', 'Usage: :listprefixes <username>'),
('commands.error.cmd_list_prefixes.user_not_found', 'User not found or not online.'),
('commands.succes.cmd_list_prefixes.header', 'Prefixes of %user%:'),
('commands.succes.cmd_list_prefixes.empty', '%user% has no prefixes.'),
-- RemovePrefix command
('commands.keys.cmd_remove_prefix', 'removeprefix'),
('commands.error.cmd_remove_prefix.usage', 'Usage: :removeprefix <username> <id|all>'),
('commands.error.cmd_remove_prefix.user_not_found', 'User not found or not online.'),
('commands.error.cmd_remove_prefix.invalid_id', 'Invalid prefix ID. Must be a number or "all".'),
('commands.error.cmd_remove_prefix.not_found', 'Prefix not found for this user.'),
('commands.succes.cmd_remove_prefix', 'Prefix #%id% removed from %user%.'),
('commands.succes.cmd_remove_prefix.all', 'All prefixes removed from %user%.'),
-- PrefixBlacklist command
('commands.keys.cmd_prefix_blacklist', 'prefixblacklist'),
('commands.error.cmd_prefix_blacklist.usage', 'Usage: :prefixblacklist <add|remove|list> [word]'),
('commands.error.cmd_prefix_blacklist.empty_word', 'Word cannot be empty.'),
('commands.succes.cmd_prefix_blacklist.header', 'Blacklisted prefix words:'),
('commands.succes.cmd_prefix_blacklist.empty', 'No blacklisted words.'),
('commands.succes.cmd_prefix_blacklist.added', 'Word "%word%" added to prefix blacklist.'),
('commands.succes.cmd_prefix_blacklist.removed', 'Word "%word%" removed from prefix blacklist.');
-- ============================================================
-- Permissions for prefix commands (add to permissions table)
-- ============================================================
INSERT IGNORE INTO `permissions` (`id`, `rank_id`, `permission_name`, `setting_type`) VALUES
(NULL, 7, 'cmd_give_prefix', '1'),
(NULL, 7, 'cmd_list_prefixes', '1'),
(NULL, 7, 'cmd_remove_prefix', '1'),
(NULL, 7, 'cmd_prefix_blacklist', '1');
@@ -7,6 +7,7 @@ import com.eu.habbo.core.*;
import com.eu.habbo.core.consolecommands.ConsoleCommand;
import com.eu.habbo.database.Database;
import com.eu.habbo.habbohotel.GameEnvironment;
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
import com.eu.habbo.networking.gameserver.GameServer;
import com.eu.habbo.networking.rconserver.RCONServer;
import com.eu.habbo.plugin.PluginManager;
@@ -319,6 +320,7 @@ public final class Emulator {
if (Emulator.pluginManager != null)
tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStartShutdownEvent()));
if (Emulator.rconServer != null) tryShutdown(() -> Emulator.rconServer.stop());
tryShutdown(() -> SessionResumeManager.getInstance().disposeAll());
if (Emulator.gameEnvironment != null) tryShutdown(() -> Emulator.gameEnvironment.dispose());
if (Emulator.pluginManager != null)
tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStoppedEvent()));
@@ -173,6 +173,9 @@ public class CatalogManager {
case mad_money:
this.put(layout.name().toLowerCase(), MadMoneyLayout.class);
break;
case custom_prefix:
this.put(layout.name().toLowerCase(), CustomPrefixLayout.class);
break;
case default_3x3:
default:
this.put("default_3x3", Default_3x3Layout.class);
@@ -43,5 +43,6 @@ public enum CatalogPageLayouts {
builders_club_loyalty,
monkey,
niko,
mad_money
mad_money,
custom_prefix
}
@@ -0,0 +1,27 @@
package com.eu.habbo.habbohotel.catalog.layouts;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.messages.ServerMessage;
import java.sql.ResultSet;
import java.sql.SQLException;
public class CustomPrefixLayout extends CatalogPage {
public CustomPrefixLayout(ResultSet set) throws SQLException {
super(set);
}
@Override
public void serialize(ServerMessage message) {
message.appendString("custom_prefix");
message.appendInt(3);
message.appendString(super.getHeaderImage());
message.appendString(super.getTeaserImage());
message.appendString(super.getSpecialImage());
message.appendInt(3);
message.appendString(super.getTextOne());
message.appendString(super.getTextDetails());
message.appendString(super.getTextTeaser());
}
}
@@ -297,8 +297,11 @@ public class CommandHandler {
addCommand(new SoftKickCommand());
addCommand(new SubscriptionCommand());
addCommand(new UpdateChatBubblesCommand());
addCommand(new GivePrefixCommand());
addCommand(new ListPrefixesCommand());
addCommand(new RemovePrefixCommand());
addCommand(new PrefixBlacklistCommand());
addCommand(new WiredCommand());
addCommand(new TestCommand());
}
@@ -0,0 +1,66 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class GivePrefixCommand extends Command {
public GivePrefixCommand() {
super("cmd_give_prefix", Emulator.getTexts().getValue("commands.keys.cmd_give_prefix").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 4) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String targetName = params[1];
String text = params[2];
String color = params[3];
String icon = params.length > 4 ? params[4] : "";
String effect = params.length > 5 ? params[5] : "";
// Validate color
String[] colorParts = color.split(",");
for (String part : colorParts) {
if (!part.matches("^#[0-9A-Fa-f]{6}$")) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.invalid_color"), RoomChatMessageBubbles.ALERT);
return true;
}
}
if (text.length() > 15) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.too_long"), RoomChatMessageBubbles.ALERT);
return true;
}
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(targetName);
if (target == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.user_not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
UserPrefix prefix = new UserPrefix(target.getHabboInfo().getId(), text, color, icon, effect);
prefix.run();
target.getInventory().getPrefixesComponent().addPrefix(prefix);
target.getClient().sendResponse(new PrefixReceivedComposer(prefix));
target.getClient().sendResponse(new UserPrefixesComposer(target));
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_give_prefix")
.replace("%user%", targetName)
.replace("%prefix%", text),
RoomChatMessageBubbles.ALERT
);
return true;
}
}
@@ -0,0 +1,59 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import java.util.List;
public class ListPrefixesCommand extends Command {
public ListPrefixesCommand() {
super("cmd_list_prefixes", Emulator.getTexts().getValue("commands.keys.cmd_list_prefixes").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 2) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_list_prefixes.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String targetName = params[1];
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(targetName);
if (target == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_list_prefixes.user_not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
List<UserPrefix> prefixes = target.getInventory().getPrefixesComponent().getPrefixes();
if (prefixes.isEmpty()) {
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_list_prefixes.empty").replace("%user%", targetName),
RoomChatMessageBubbles.ALERT
);
return true;
}
StringBuilder sb = new StringBuilder();
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_list_prefixes.header").replace("%user%", targetName)).append("\r");
for (UserPrefix prefix : prefixes) {
sb.append("ID: ").append(prefix.getId())
.append(" | {").append(prefix.getText()).append("}")
.append(" | Color: ").append(prefix.getColor())
.append(prefix.getIcon().isEmpty() ? "" : " | Icon: " + prefix.getIcon())
.append(prefix.getEffect().isEmpty() ? "" : " | Effect: " + prefix.getEffect())
.append(prefix.isActive() ? " [ACTIVE]" : "")
.append("\r");
}
gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT);
return true;
}
}
@@ -0,0 +1,98 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PrefixBlacklistCommand extends Command {
private static final Logger LOGGER = LoggerFactory.getLogger(PrefixBlacklistCommand.class);
public PrefixBlacklistCommand() {
super("cmd_prefix_blacklist", Emulator.getTexts().getValue("commands.keys.cmd_prefix_blacklist").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 2) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String action = params[1].toLowerCase();
if (action.equals("list")) {
StringBuilder sb = new StringBuilder();
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.header")).append("\r");
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist ORDER BY word")) {
try (ResultSet set = statement.executeQuery()) {
int count = 0;
while (set.next()) {
sb.append("- ").append(set.getString("word")).append("\r");
count++;
}
if (count == 0) {
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.empty"));
}
}
} catch (SQLException e) {
LOGGER.error("Error listing prefix blacklist", e);
}
gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT);
return true;
}
if (params.length < 3) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String word = params[2].toLowerCase().trim();
if (word.isEmpty()) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.empty_word"), RoomChatMessageBubbles.ALERT);
return true;
}
if (action.equals("add")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("INSERT INTO custom_prefix_blacklist (word) VALUES (?)")) {
statement.setString(1, word);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Error adding prefix blacklist word", e);
}
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.added").replace("%word%", word),
RoomChatMessageBubbles.ALERT
);
} else if (action.equals("remove")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM custom_prefix_blacklist WHERE word = ?")) {
statement.setString(1, word);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Error removing prefix blacklist word", e);
}
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.removed").replace("%word%", word),
RoomChatMessageBubbles.ALERT
);
} else {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
}
return true;
}
}
@@ -0,0 +1,81 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
import java.util.List;
public class RemovePrefixCommand extends Command {
public RemovePrefixCommand() {
super("cmd_remove_prefix", Emulator.getTexts().getValue("commands.keys.cmd_remove_prefix").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 3) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String targetName = params[1];
String prefixIdStr = params[2];
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(targetName);
if (target == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.user_not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
if (prefixIdStr.equalsIgnoreCase("all")) {
List<UserPrefix> prefixes = target.getInventory().getPrefixesComponent().getPrefixes();
for (UserPrefix prefix : prefixes) {
prefix.needsDelete(true);
Emulator.getThreading().run(prefix);
}
// Clear in-memory
for (UserPrefix prefix : prefixes) {
target.getInventory().getPrefixesComponent().removePrefix(prefix);
}
target.getClient().sendResponse(new UserPrefixesComposer(target));
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_remove_prefix.all").replace("%user%", targetName),
RoomChatMessageBubbles.ALERT
);
} else {
int prefixId;
try {
prefixId = Integer.parseInt(prefixIdStr);
} catch (NumberFormatException e) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.invalid_id"), RoomChatMessageBubbles.ALERT);
return true;
}
UserPrefix prefix = target.getInventory().getPrefixesComponent().getPrefix(prefixId);
if (prefix == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
target.getInventory().getPrefixesComponent().removePrefix(prefix);
prefix.needsDelete(true);
Emulator.getThreading().run(prefix);
target.getClient().sendResponse(new UserPrefixesComposer(target));
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_remove_prefix").replace("%user%", targetName).replace("%id%", String.valueOf(prefixId)),
RoomChatMessageBubbles.ALERT
);
}
return true;
}
}
@@ -26,6 +26,7 @@ public class GameClient {
private Habbo habbo;
private boolean handshakeFinished;
private String machineId = "";
private String ssoTicket = "";
public final ConcurrentHashMap<Integer, Integer> incomingPacketCounter = new ConcurrentHashMap<>(25);
public final ConcurrentHashMap<Class<? extends MessageHandler>, Long> messageTimestamps = new ConcurrentHashMap<>();
@@ -82,6 +83,14 @@ public class GameClient {
this.machineId = machineId;
}
public String getSsoTicket() {
return this.ssoTicket;
}
public void setSsoTicket(String ssoTicket) {
this.ssoTicket = ssoTicket != null ? ssoTicket : "";
}
public void sendResponse(MessageComposer composer) {
this.sendResponse(composer.compose());
}
@@ -145,8 +154,15 @@ public class GameClient {
if (this.habbo != null) {
if (this.habbo.isOnline()) {
this.habbo.getHabboInfo().setOnline(false);
this.habbo.disconnect();
// Try to park the habbo in the grace period instead of immediate disconnect
boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
if (!parked) {
// No grace period configured — immediate disconnect as before
this.habbo.getHabboInfo().setOnline(false);
this.habbo.disconnect();
}
// If parked, do NOT call disconnect() — the habbo stays in the room
}
this.habbo = null;
@@ -116,6 +116,22 @@ public class GameClientManager {
}
/**
* Find an existing GameClient that authenticated with the given SSO ticket.
* Used to detect reconnections where the old connection hasn't been closed yet.
*/
public GameClient findClientBySsoTicket(String ssoTicket) {
if (ssoTicket == null || ssoTicket.isEmpty()) return null;
for (GameClient client : this.clients.values()) {
if (ssoTicket.equals(client.getSsoTicket()) && client.getHabbo() != null) {
return client;
}
}
return null;
}
public List<Habbo> getHabbosWithMachineId(String machineId) {
List<Habbo> habbos = new ArrayList<>();
@@ -0,0 +1,173 @@
package com.eu.habbo.habbohotel.gameclients;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
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 {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class);
private static SessionResumeManager instance;
private final ConcurrentHashMap<Integer, GhostSession> ghostSessions = new ConcurrentHashMap<>();
public static SessionResumeManager getInstance() {
if (instance == null) {
instance = new SessionResumeManager();
}
return instance;
}
public int getGracePeriodSeconds() {
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) {
int graceSeconds = getGracePeriodSeconds();
if (graceSeconds <= 0) {
return false;
}
int userId = habbo.getHabboInfo().getId();
// Cancel any existing ghost session for this user
GhostSession existing = ghostSessions.remove(userId);
if (existing != null && existing.disposeFuture != null) {
existing.disposeFuture.cancel(false);
}
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
habbo.getHabboInfo().getUsername(), userId, graceSeconds);
// Restore the SSO ticket so the client can reconnect with the same ticket
if (ssoTicket != null && !ssoTicket.isEmpty()) {
restoreSsoTicket(userId, ssoTicket);
}
// Schedule the final disconnect after the grace period
ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
GhostSession ghost = ghostSessions.remove(userId);
if (ghost != null) {
LOGGER.info("[SessionResume] Grace period expired for {} (id={}) - performing full disconnect",
ghost.habbo.getHabboInfo().getUsername(), userId);
performFullDisconnect(ghost.habbo);
}
}, graceSeconds * 1000);
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future));
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) {
GhostSession ghost = ghostSessions.remove(userId);
if (ghost == null) {
return null;
}
// Cancel the scheduled dispose
if (ghost.disposeFuture != null) {
ghost.disposeFuture.cancel(false);
}
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
ghost.habbo.getHabboInfo().getUsername(), userId);
return ghost.habbo;
}
/**
* Check if a user has a ghost session (is in grace period).
*/
public boolean hasGhostSession(int userId) {
return ghostSessions.containsKey(userId);
}
/**
* Immediately expire all ghost sessions (e.g. on emulator shutdown).
*/
public void disposeAll() {
for (GhostSession ghost : ghostSessions.values()) {
if (ghost.disposeFuture != null) {
ghost.disposeFuture.cancel(false);
}
performFullDisconnect(ghost.habbo);
}
ghostSessions.clear();
}
/**
* Perform the actual full disconnect that normally happens in Habbo.disconnect().
*/
private void performFullDisconnect(Habbo habbo) {
try {
habbo.getHabboInfo().setOnline(false);
habbo.disconnect();
} catch (Exception 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());
}
private void restoreSsoTicket(int userId, String ssoTicket) {
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
statement.setString(1, ssoTicket);
statement.setInt(2, userId);
statement.execute();
LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId);
} catch (Exception e) {
LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e);
}
}
private void clearSsoTicket(int userId) {
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
statement.setString(1, "");
statement.setInt(2, userId);
statement.execute();
} catch (Exception e) {
LOGGER.error("[SessionResume] Failed to clear SSO ticket for user " + userId, e);
}
}
private static class GhostSession {
final Habbo habbo;
final String ssoTicket;
final ScheduledFuture<?> disposeFuture;
GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture<?> disposeFuture) {
this.habbo = habbo;
this.ssoTicket = ssoTicket;
this.disposeFuture = disposeFuture;
}
}
}
@@ -49,29 +49,13 @@ import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemLegs;
import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemPlanet;
import com.eu.habbo.habbohotel.items.interactions.wired.conditions.*;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.*;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniArea;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersArea;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersNeighborhood;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniNeighborhood;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniByType;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniAltitude;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniOnFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniPicks;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniSignal;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersSignal;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersByType;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersTeam;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersByAction;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersByName;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersHandItem;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersOnFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersGroup;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.*;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.*;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
@@ -164,6 +164,14 @@ public class WiredConditionHabboHasHandItem extends InteractionWiredCondition {
return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL;
}
protected int getHandItem() {
return this.handItem;
}
protected int getUserSource() {
return this.userSource;
}
static class JsonData {
int handItemId;
int userSource;
@@ -19,7 +19,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger {
private static final WiredTriggerType type = WiredTriggerType.STATE_CHANGED;
@@ -60,7 +59,6 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger {
if (snapshot == null) {
return false;
}
return snapshot.state.equals(this.normalizeState(sourceItem.getExtradata()));
}
@@ -234,15 +232,24 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger {
}
private boolean matchesSourceItem(WiredEvent event, HabboItem sourceItem) {
List<HabboItem> selectedItems = event.getRoom() == null
? new ArrayList<>()
: this.snapshots.stream()
.map(snapshot -> event.getRoom().getHabboItem(snapshot.itemId))
.filter(item -> item != null)
.collect(Collectors.toList());
List<HabboItem> selectedItems = new ArrayList<>();
return WiredTriggerSourceUtil.resolveItems(this, event, this.furniSource, selectedItems).stream()
.anyMatch(item -> item != null && item.getId() == sourceItem.getId());
if (event.getRoom() != null) {
for (StateSnapshot snapshot : this.snapshots) {
HabboItem item = event.getRoom().getHabboItem(snapshot.itemId);
if (item != null) {
selectedItems.add(item);
}
}
}
for (HabboItem item : WiredTriggerSourceUtil.resolveItems(this, event, this.furniSource, selectedItems)) {
if (item != null && item.getId() == sourceItem.getId()) {
return true;
}
}
return false;
}
private int normalizeFurniSource(int value) {
@@ -202,6 +202,25 @@ public class RoomChatMessage implements Runnable, ISerialize, DatabaseLoggable {
message.appendInt(0);
message.appendString(this.RoomChatColour); //Added packet for room chat
message.appendInt(this.getMessage().length());
// Custom prefix data
String prefixText = "";
String prefixColor = "";
String prefixIcon = "";
String prefixEffect = "";
if (this.habbo != null && this.habbo.getInventory() != null && this.habbo.getInventory().getPrefixesComponent() != null) {
com.eu.habbo.habbohotel.users.UserPrefix activePrefix = this.habbo.getInventory().getPrefixesComponent().getActivePrefix();
if (activePrefix != null) {
prefixText = activePrefix.getText();
prefixColor = activePrefix.getColor();
prefixIcon = activePrefix.getIcon();
prefixEffect = activePrefix.getEffect();
}
}
message.appendString(prefixText);
message.appendString(prefixColor);
message.appendString(prefixIcon);
message.appendString(prefixEffect);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
}
@@ -292,7 +292,7 @@ public class RoomManager {
/**
* Loads a room, optionally loading its data.
* If the room is already being loaded in the background, this will wait for that to complete.
*
*
* @param id The room ID
* @param loadData Whether to load room data (items, bots, pets, etc.)
* @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) {
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) {
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) {
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);
if (room == null)
@@ -547,7 +551,7 @@ public class RoomManager {
room.hasRights(habbo) ||
(room.getState().equals(RoomState.INVISIBLE) && room.hasRights(habbo)) ||
(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) {
boolean rightsFound = false;
@@ -572,7 +576,7 @@ public class RoomManager {
room.addToQueue(habbo);
} else if (room.getState() == RoomState.PASSWORD) {
if (room.getPassword().equalsIgnoreCase(password))
this.openRoom(habbo, room, doorLocation);
this.openRoom(habbo, room, doorLocation, isReconnectSpawn);
else {
habbo.getClient().sendResponse(new GenericErrorMessagesComposer(GenericErrorMessagesComposer.WRONG_PASSWORD_USED));
habbo.getClient().sendResponse(new HotelViewComposer());
@@ -585,6 +589,10 @@ public class RoomManager {
}
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)
return;
@@ -623,7 +631,13 @@ public class RoomManager {
if (doorLocation == null) {
habbo.getRoomUnit().setBodyRotation(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 {
// Furniture teleport spawn
habbo.getRoomUnit().setCanLeaveRoomByDoor(false);
habbo.getRoomUnit().isTeleporting = true;
HabboItem topItem = room.getTopItemAt(doorLocation.x, doorLocation.y);
@@ -22,6 +22,7 @@ public class HabboInventory {
private EffectsComponent effectsComponent;
private ItemsComponent itemsComponent;
private PetsComponent petsComponent;
private PrefixesComponent prefixesComponent;
public HabboInventory(Habbo habbo) {
this.habbo = habbo;
@@ -61,6 +62,12 @@ public class HabboInventory {
LOGGER.error("Caught exception", e);
}
try {
this.prefixesComponent = new PrefixesComponent(this.habbo);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
}
this.items = MarketPlace.getOwnOffers(this.habbo);
}
@@ -112,6 +119,14 @@ public class HabboInventory {
this.petsComponent = petsComponent;
}
public PrefixesComponent getPrefixesComponent() {
return this.prefixesComponent;
}
public void setPrefixesComponent(PrefixesComponent prefixesComponent) {
this.prefixesComponent = prefixesComponent;
}
public void dispose() {
this.badgesComponent.dispose();
this.botsComponent.dispose();
@@ -119,6 +134,7 @@ public class HabboInventory {
this.itemsComponent.dispose();
this.petsComponent.dispose();
this.wardrobeComponent.dispose();
this.prefixesComponent.dispose();
this.badgesComponent = null;
this.botsComponent = null;
@@ -126,6 +142,7 @@ public class HabboInventory {
this.itemsComponent = null;
this.petsComponent = null;
this.wardrobeComponent = null;
this.prefixesComponent = null;
}
public void addMarketplaceOffer(MarketPlaceOffer marketPlaceOffer) {
@@ -0,0 +1,122 @@
package com.eu.habbo.habbohotel.users;
import com.eu.habbo.Emulator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
public class UserPrefix implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(UserPrefix.class);
private int id;
private final int userId;
private String text;
private String color;
private String icon;
private String effect;
private boolean active;
private boolean needsInsert;
private boolean needsUpdate;
private boolean needsDelete;
public UserPrefix(ResultSet set) throws SQLException {
this.id = set.getInt("id");
this.userId = set.getInt("user_id");
this.text = set.getString("text");
this.color = set.getString("color");
this.icon = set.getString("icon");
if (this.icon == null) this.icon = "";
this.effect = set.getString("effect");
if (this.effect == null) this.effect = "";
this.active = set.getBoolean("active");
this.needsInsert = false;
this.needsUpdate = false;
this.needsDelete = false;
}
public UserPrefix(int userId, String text, String color, String icon, String effect) {
this.id = 0;
this.userId = userId;
this.text = text;
this.color = color;
this.icon = icon != null ? icon : "";
this.effect = effect != null ? effect : "";
this.active = false;
this.needsInsert = true;
this.needsUpdate = false;
this.needsDelete = false;
}
@Override
public void run() {
try {
if (this.needsInsert) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO user_prefixes (user_id, text, color, icon, effect, active) VALUES (?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, this.userId);
statement.setString(2, this.text);
statement.setString(3, this.color);
statement.setString(4, this.icon);
statement.setString(5, this.effect);
statement.setBoolean(6, this.active);
statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) {
if (set.next()) {
this.id = set.getInt(1);
}
}
}
this.needsInsert = false;
} else if (this.needsDelete) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"DELETE FROM user_prefixes WHERE id = ? AND user_id = ?")) {
statement.setInt(1, this.id);
statement.setInt(2, this.userId);
statement.execute();
}
this.needsDelete = false;
} else if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE user_prefixes SET text = ?, color = ?, icon = ?, effect = ?, active = ? WHERE id = ? AND user_id = ?")) {
statement.setString(1, this.text);
statement.setString(2, this.color);
statement.setString(3, this.icon);
statement.setString(4, this.effect);
statement.setBoolean(5, this.active);
statement.setInt(6, this.id);
statement.setInt(7, this.userId);
statement.execute();
}
this.needsUpdate = false;
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public int getId() { return this.id; }
public int getUserId() { return this.userId; }
public String getText() { return this.text; }
public void setText(String text) { this.text = text; }
public String getColor() { return this.color; }
public void setColor(String color) { this.color = color; }
public String getIcon() { return this.icon; }
public void setIcon(String icon) { this.icon = icon != null ? icon : ""; }
public String getEffect() { return this.effect; }
public void setEffect(String effect) { this.effect = effect != null ? effect : ""; }
public boolean isActive() { return this.active; }
public void setActive(boolean active) {
this.active = active;
this.needsUpdate = true;
}
public void needsUpdate(boolean needsUpdate) { this.needsUpdate = needsUpdate; }
public void needsInsert(boolean needsInsert) { this.needsInsert = needsInsert; }
public void needsDelete(boolean needsDelete) { this.needsDelete = needsDelete; }
}
@@ -0,0 +1,105 @@
package com.eu.habbo.habbohotel.users.inventory;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class PrefixesComponent {
private static final Logger LOGGER = LoggerFactory.getLogger(PrefixesComponent.class);
private final List<UserPrefix> prefixes = new ArrayList<>();
private final Habbo habbo;
public PrefixesComponent(Habbo habbo) {
this.habbo = habbo;
this.loadPrefixes();
}
private void loadPrefixes() {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM user_prefixes WHERE user_id = ?")) {
statement.setInt(1, this.habbo.getHabboInfo().getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
this.prefixes.add(new UserPrefix(set));
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public List<UserPrefix> getPrefixes() {
synchronized (this.prefixes) {
return new ArrayList<>(this.prefixes);
}
}
public UserPrefix getActivePrefix() {
synchronized (this.prefixes) {
for (UserPrefix prefix : this.prefixes) {
if (prefix.isActive()) return prefix;
}
}
return null;
}
public UserPrefix getPrefix(int id) {
synchronized (this.prefixes) {
for (UserPrefix prefix : this.prefixes) {
if (prefix.getId() == id) return prefix;
}
}
return null;
}
public void addPrefix(UserPrefix prefix) {
synchronized (this.prefixes) {
this.prefixes.add(prefix);
}
}
public void removePrefix(UserPrefix prefix) {
synchronized (this.prefixes) {
this.prefixes.remove(prefix);
}
}
public void setActive(int prefixId) {
synchronized (this.prefixes) {
for (UserPrefix prefix : this.prefixes) {
boolean shouldBeActive = prefix.getId() == prefixId;
if (prefix.isActive() != shouldBeActive) {
prefix.setActive(shouldBeActive);
Emulator.getThreading().run(prefix);
}
}
}
}
public void deactivateAll() {
synchronized (this.prefixes) {
for (UserPrefix prefix : this.prefixes) {
if (prefix.isActive()) {
prefix.setActive(false);
Emulator.getThreading().run(prefix);
}
}
}
}
public void dispose() {
synchronized (this.prefixes) {
this.prefixes.clear();
}
}
}
@@ -296,7 +296,6 @@ public final class WiredManager {
private static String getPendingFurniClickKey(Room room, RoomUnit user, HabboItem item) {
return room.getId() + ":" + user.getId() + ":" + item.getId();
}
/**
* Trigger when a user clicks invisible click tile furniture.
*/
@@ -318,7 +317,6 @@ public final class WiredManager {
}
WiredTriggerHabboClicksUser.clearRuntimeFlags(clickingUser);
WiredEvent event = WiredEvents.userClicksUser(room, clickingUser, clickedUser);
return handleEvent(event);
}
@@ -444,6 +442,30 @@ public final class WiredManager {
return handleEvent(event);
}
/**
* Trigger a long periodic timer.
*/
public static boolean triggerTimerRepeatLong(Room room, HabboItem timerItem) {
if (!isEnabled() || room == null) {
return false;
}
WiredEvent event = WiredEvents.timerRepeatLong(room, timerItem);
return handleEvent(event);
}
/**
* Trigger a short periodic timer.
*/
public static boolean triggerTimerRepeatShort(Room room, HabboItem timerItem) {
if (!isEnabled() || room == null) {
return false;
}
WiredEvent event = WiredEvents.timerRepeatShort(room, timerItem);
return handleEvent(event);
}
/**
* Trigger game start.
*/
@@ -34,6 +34,7 @@ import com.eu.habbo.messages.incoming.helper.MySanctionStatusEvent;
import com.eu.habbo.messages.incoming.helper.RequestTalentTrackEvent;
import com.eu.habbo.messages.incoming.hotelview.*;
import com.eu.habbo.messages.incoming.inventory.*;
import com.eu.habbo.messages.incoming.inventory.prefixes.*;
import com.eu.habbo.messages.incoming.modtool.*;
import com.eu.habbo.messages.incoming.navigator.*;
import com.eu.habbo.messages.incoming.polls.AnswerPollEvent;
@@ -370,6 +371,12 @@ public class PacketManager {
this.registerHandler(Incoming.RequestInventoryPetsEvent, RequestInventoryPetsEvent.class);
this.registerHandler(Incoming.RequestInventoryPetDelete, RequestInventoryPetDelete.class);
this.registerHandler(Incoming.RequestInventoryBadgeDelete, RequestInventoryBadgeDelete.class);
// Custom Prefixes
this.registerHandler(Incoming.RequestUserPrefixesEvent, RequestUserPrefixesEvent.class);
this.registerHandler(Incoming.SetActivePrefixEvent, SetActivePrefixEvent.class);
this.registerHandler(Incoming.DeletePrefixEvent, DeletePrefixEvent.class);
this.registerHandler(Incoming.PurchasePrefixEvent, PurchasePrefixEvent.class);
}
void registerRooms() throws Exception {
@@ -380,7 +380,7 @@ public class Incoming {
public static final int UNKNOWN_SNOWSTORM_6000 = 6000;
public static final int UNKNOWN_SNOWSTORM_6001 = 6001;
public static final int UNKNOWN_SNOWSTORM_6002 = 6002;
// public static final int UNKNOWN_SNOWSTORM_6002 = 6002;
public static final int UNKNOWN_SNOWSTORM_6003 = 6003;
public static final int UNKNOWN_SNOWSTORM_6004 = 6004;
public static final int UNKNOWN_SNOWSTORM_6005 = 6005;
@@ -411,4 +411,10 @@ public class Incoming {
public static final int ClickUserEvent = 10020;
public static final int RequestInventoryPetDelete = 10030;
public static final int RequestInventoryBadgeDelete = 10031;
// Custom Prefixes
public static final int RequestUserPrefixesEvent = 7011;
public static final int SetActivePrefixEvent = 7012;
public static final int DeletePrefixEvent = 7013;
public static final int PurchasePrefixEvent = 7014;
}
@@ -9,6 +9,11 @@ import com.eu.habbo.messages.incoming.MessageHandler;
public class PurchaseTargetOfferEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 1000;
}
@Override
public void handle() throws Exception {
final int offerId = this.packet.readInt();
@@ -4,6 +4,11 @@ import com.eu.habbo.habbohotel.catalog.marketplace.MarketPlace;
import com.eu.habbo.messages.incoming.MessageHandler;
public class BuyItemEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 1000;
}
@Override
public void handle() throws Exception {
int offerId = this.packet.readInt();
@@ -4,6 +4,11 @@ import com.eu.habbo.habbohotel.catalog.marketplace.MarketPlace;
import com.eu.habbo.messages.incoming.MessageHandler;
public class RequestCreditsEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
MarketPlace.getCredits(this.client);
@@ -4,6 +4,11 @@ import com.eu.habbo.habbohotel.catalog.marketplace.MarketPlace;
import com.eu.habbo.messages.incoming.MessageHandler;
public class TakeBackItemEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
int offerId = this.packet.readInt();
@@ -2,10 +2,13 @@ package com.eu.habbo.messages.incoming.handshake;
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.SessionResumeManager;
import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem;
import com.eu.habbo.habbohotel.modtool.ModToolSanctions;
import com.eu.habbo.habbohotel.navigation.NavigatorSavedSearch;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomManager;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboManager;
@@ -14,6 +17,7 @@ import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionHabboClub;
import com.eu.habbo.messages.NoAuthMessage;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.commands.AvailableCommandsComposer;
import com.eu.habbo.messages.outgoing.gamecenter.GameCenterAccountInfoComposer;
import com.eu.habbo.messages.outgoing.gamecenter.GameCenterGameListComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
@@ -81,31 +85,94 @@ public class SecureLoginEvent extends MessageHandler {
}
if (this.client.getHabbo() == null) {
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
// Store SSO ticket on client for grace period tracking
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);
if (existingClient != null && existingClient != this.client) {
LOGGER.info("[SessionResume] Found existing client with same SSO ticket — disposing old connection to trigger parking");
Emulator.getGameServer().getGameClientManager().disposeClient(existingClient);
}
// First, look up the user ID to check for ghost sessions
int lookupUserId = 0;
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) {
stmt.setString(1, sso);
try (java.sql.ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
lookupUserId = rs.getInt("id");
}
}
} catch (Exception 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;
boolean isSessionResume = false;
if (lookupUserId > 0) {
habbo = SessionResumeManager.getInstance().resumeSession(lookupUserId);
}
if (habbo != null) {
try {
habbo.setClient(this.client);
this.client.setHabbo(habbo);
if(!this.client.getHabbo().connect()) {
// Session resume reattach the existing Habbo to the new client
isSessionResume = true;
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
habbo.getHabboInfo().getUsername(), habbo.getHabboInfo().getId());
habbo.setClient(this.client);
this.client.setHabbo(habbo);
this.client.setMachineId(habbo.getHabboInfo().getMachineID());
// Clear the SSO ticket now that session is resumed (prevent reuse)
if (!Emulator.debugging) {
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection();
java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
stmt.setString(1, "");
stmt.setInt(2, habbo.getHabboInfo().getId());
stmt.execute();
} catch (Exception e) {
LOGGER.error("Failed to clear SSO ticket after session resume", e);
}
}
} else {
// Normal login load from database
habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
}
if (habbo != null) {
if (!isSessionResume) {
try {
habbo.setClient(this.client);
this.client.setHabbo(habbo);
if(!this.client.getHabbo().connect()) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if (this.client.getHabbo().getHabboInfo() == null) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if (this.client.getHabbo().getHabboInfo().getRank() == null) {
throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!");
}
Emulator.getThreading().run(habbo);
Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if (this.client.getHabbo().getHabboInfo() == null) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if (this.client.getHabbo().getHabboInfo().getRank() == null) {
throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!");
}
Emulator.getThreading().run(habbo);
Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if(ClothingValidationManager.VALIDATE_ON_LOGIN) {
@@ -121,7 +188,18 @@ public class SecureLoginEvent extends MessageHandler {
int roomIdToEnter = 0;
if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && this.client.getHabbo().getHabboInfo().getHomeRoom() != 0)
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();
if (currentRoom != null) {
LOGGER.info("[SessionResume] {} is still in room {} — client will resume in-place",
habbo.getHabboInfo().getUsername(), currentRoom.getId());
}
} else if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && this.client.getHabbo().getHabboInfo().getHomeRoom() != 0)
roomIdToEnter = this.client.getHabbo().getHabboInfo().getHomeRoom();
else if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && RoomManager.HOME_ROOM_ID > 0)
roomIdToEnter = RoomManager.HOME_ROOM_ID;
@@ -131,6 +209,11 @@ public class SecureLoginEvent extends MessageHandler {
messages.add(new UserClothesComposer(this.client.getHabbo()).compose());
messages.add(new NewUserIdentityComposer(habbo).compose());
messages.add(new UserPermissionsComposer(this.client.getHabbo()).compose());
messages.add(new AvailableCommandsComposer(
Emulator.getGameEnvironment().getCommandHandler().getCommandsForRank(
this.client.getHabbo().getHabboInfo().getRank().getId()
)
).compose());
messages.add(new AvailabilityStatusMessageComposer(true, false, true).compose());
messages.add(new PingComposer().compose());
messages.add(new EnableNotificationsComposer(Emulator.getConfig().getBoolean("bubblealerts.enabled", true)).compose());
@@ -189,42 +272,45 @@ public class SecureLoginEvent extends MessageHandler {
}
}
UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin());
Emulator.getPluginManager().fireEvent(userLoginEvent);
// Skip login-only events on session resume (welcome alerts, login events, etc.)
if (!isSessionResume) {
UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin());
Emulator.getPluginManager().fireEvent(userLoginEvent);
if(userLoginEvent.isCancelled()) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if(userLoginEvent.isCancelled()) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
return;
}
if (Emulator.getConfig().getBoolean("hotel.welcome.alert.enabled")) {
final Habbo finalHabbo = habbo;
Emulator.getThreading().run(() -> {
if (Emulator.getConfig().getBoolean("hotel.welcome.alert.oldstyle")) {
SecureLoginEvent.this.client.sendResponse(new MessagesForYouComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()).split("<br/>")));
} else {
SecureLoginEvent.this.client.sendResponse(new GenericAlertComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername())));
}
}, Emulator.getConfig().getInt("hotel.welcome.alert.delay", 5000));
}
if (Emulator.getConfig().getBoolean("hotel.welcome.alert.enabled")) {
final Habbo finalHabbo = habbo;
Emulator.getThreading().run(() -> {
if (Emulator.getConfig().getBoolean("hotel.welcome.alert.oldstyle")) {
SecureLoginEvent.this.client.sendResponse(new MessagesForYouComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()).split("<br/>")));
} else {
SecureLoginEvent.this.client.sendResponse(new GenericAlertComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername())));
}
}, Emulator.getConfig().getInt("hotel.welcome.alert.delay", 5000));
}
if(SubscriptionHabboClub.HC_PAYDAY_ENABLED) {
SubscriptionHabboClub.processUnclaimed(habbo);
}
if(SubscriptionHabboClub.HC_PAYDAY_ENABLED) {
SubscriptionHabboClub.processUnclaimed(habbo);
}
SubscriptionHabboClub.processClubBadge(habbo);
SubscriptionHabboClub.processClubBadge(habbo);
Messenger.checkFriendSizeProgress(habbo);
Messenger.checkFriendSizeProgress(habbo);
if (!habbo.getHabboStats().hasGottenDefaultSavedSearches) {
habbo.getHabboStats().hasGottenDefaultSavedSearches = true;
Emulator.getThreading().run(habbo.getHabboStats());
if (!habbo.getHabboStats().hasGottenDefaultSavedSearches) {
habbo.getHabboStats().hasGottenDefaultSavedSearches = true;
Emulator.getThreading().run(habbo.getHabboStats());
habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("official-root", ""));
habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("my", ""));
habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("favorites", ""));
habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("official-root", ""));
habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("my", ""));
habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("favorites", ""));
this.client.sendResponse(new NewNavigatorSavedSearchesComposer(this.client.getHabbo().getHabboInfo().getSavedSearches()));
this.client.sendResponse(new NewNavigatorSavedSearchesComposer(this.client.getHabbo().getHabboInfo().getSavedSearches()));
}
}
} else {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
@@ -0,0 +1,23 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class DeletePrefixEvent extends MessageHandler {
@Override
public void handle() throws Exception {
int prefixId = this.packet.readInt();
UserPrefix prefix = this.client.getHabbo().getInventory().getPrefixesComponent().getPrefix(prefixId);
if (prefix == null) return;
this.client.getHabbo().getInventory().getPrefixesComponent().removePrefix(prefix);
prefix.needsDelete(true);
Emulator.getThreading().run(prefix);
this.client.sendResponse(new UserPrefixesComposer(this.client.getHabbo()));
}
}
@@ -0,0 +1,145 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer;
import com.eu.habbo.messages.outgoing.users.UserCreditsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PurchasePrefixEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class);
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
String text = this.packet.readString();
String color = this.packet.readString();
String icon = this.packet.readString();
String effect = this.packet.readString();
Habbo habbo = this.client.getHabbo();
if (habbo == null) return;
// Load settings
int maxLength = getSettingInt("max_length", 15);
int minRank = getSettingInt("min_rank_to_buy", 1);
int priceCredits = getSettingInt("price_credits", 5);
int pricePoints = getSettingInt("price_points", 0);
int pointsType = getSettingInt("points_type", 0);
// Validate text
text = text.trim();
if (text.isEmpty() || text.length() > maxLength) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Prefix text is invalid or too long (max " + maxLength + " characters)."));
return;
}
// Validate color (single hex or comma-separated multi hex for per-letter colors)
String[] colorParts = color.split(",");
for (String part : colorParts) {
if (!part.matches("^#[0-9A-Fa-f]{6}$")) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid color format."));
return;
}
}
// Check rank
if (habbo.getHabboInfo().getRank().getId() < minRank) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Your rank is too low to purchase prefixes."));
return;
}
// Check blacklist
if (isBlacklisted(text)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "This prefix contains a blocked word."));
return;
}
// Check credits
if (priceCredits > 0 && habbo.getHabboInfo().getCredits() < priceCredits) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough credits."));
return;
}
// Check points
if (pricePoints > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < pricePoints) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
return;
}
// Deduct currency
if (priceCredits > 0) {
habbo.getHabboInfo().addCredits(-priceCredits);
this.client.sendResponse(new UserCreditsComposer(habbo));
}
if (pricePoints > 0) {
habbo.getHabboInfo().addCurrencyAmount(pointsType, -pricePoints);
this.client.sendResponse(new UserCurrencyComposer(habbo));
}
// Validate icon (allow empty or known icon names)
if (icon == null) icon = "";
icon = icon.trim();
// Validate effect
if (effect == null) effect = "";
effect = effect.trim();
// Create prefix
UserPrefix prefix = new UserPrefix(habbo.getHabboInfo().getId(), text, color, icon, effect);
prefix.run(); // Insert into DB synchronously to get the ID
habbo.getInventory().getPrefixesComponent().addPrefix(prefix);
this.client.sendResponse(new PrefixReceivedComposer(prefix));
}
private int getSettingInt(String key, int defaultValue) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT `value` FROM custom_prefix_settings WHERE key_name = ?")) {
statement.setString(1, key);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
return Integer.parseInt(set.getString("value"));
}
}
} catch (SQLException | NumberFormatException e) {
LOGGER.error("Error reading prefix setting: " + key, e);
}
return defaultValue;
}
private boolean isBlacklisted(String text) {
String lowerText = text.toLowerCase();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist")) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
if (lowerText.contains(set.getString("word").toLowerCase())) {
return true;
}
}
}
} catch (SQLException e) {
LOGGER.error("Error checking prefix blacklist", e);
}
return false;
}
}
@@ -0,0 +1,11 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class RequestUserPrefixesEvent extends MessageHandler {
@Override
public void handle() throws Exception {
this.client.sendResponse(new UserPrefixesComposer(this.client.getHabbo()));
}
}
@@ -0,0 +1,25 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer;
public class SetActivePrefixEvent extends MessageHandler {
@Override
public void handle() throws Exception {
int prefixId = this.packet.readInt();
if (prefixId == 0) {
this.client.getHabbo().getInventory().getPrefixesComponent().deactivateAll();
this.client.sendResponse(new ActivePrefixUpdatedComposer(null));
return;
}
UserPrefix prefix = this.client.getHabbo().getInventory().getPrefixesComponent().getPrefix(prefixId);
if (prefix == null) return;
this.client.getHabbo().getInventory().getPrefixesComponent().setActive(prefixId);
this.client.sendResponse(new ActivePrefixUpdatedComposer(prefix));
}
}
@@ -7,6 +7,11 @@ import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.incoming.MessageHandler;
public class ModToolIssueDefaultSanctionEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) {
@@ -4,6 +4,11 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.messages.incoming.MessageHandler;
public class ModToolKickEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
Emulator.getGameEnvironment().getModToolManager().kick(this.client.getHabbo(), Emulator.getGameEnvironment().getHabboManager().getHabbo(this.packet.readInt()), this.packet.readString());
@@ -12,6 +12,11 @@ import gnu.trove.map.hash.THashMap;
import java.util.ArrayList;
public class ModToolSanctionAlertEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
int userId = this.packet.readInt();
@@ -12,6 +12,11 @@ import gnu.trove.map.hash.THashMap;
import java.util.ArrayList;
public class ModToolSanctionBanEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
public static final int BAN_18_HOURS = 3;
public static final int BAN_7_DAYS = 4;
public static final int BAN_30_DAYS_STEP_1 = 5;
@@ -14,6 +14,11 @@ import java.util.ArrayList;
import java.util.Date;
public class ModToolSanctionMuteEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
int userId = this.packet.readInt();
@@ -12,6 +12,11 @@ import gnu.trove.map.hash.THashMap;
import java.util.ArrayList;
public class ModToolSanctionTradeLockEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
int userId = this.packet.readInt();
@@ -8,6 +8,11 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.plugin.events.support.SupportUserAlertedReason;
public class ModToolWarnEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 2000;
}
@Override
public void handle() throws Exception {
if (this.client.getHabbo().hasPermission(Permission.ACC_SUPPORTTOOL)) {
@@ -13,6 +13,10 @@ import org.slf4j.LoggerFactory;
public class RequestCreateRoomEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestCreateRoomEvent.class);
@Override
public int getRatelimit() {
return 3000;
}
@Override
public void handle() throws Exception {
@@ -2,18 +2,38 @@ package com.eu.habbo.messages.incoming.rooms;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.messages.incoming.MessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RequestRoomLoadEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestRoomLoadEvent.class);
@Override
public void handle() throws Exception {
int roomId = this.packet.readInt();
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)
if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0
&& this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) {
if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0
&& this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) {
this.client.getHabbo().getHabboInfo().setLoadingRoom(0);
}
@@ -30,6 +50,18 @@ public class RequestRoomLoadEvent extends MessageHandler {
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
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());
room.removeHabbo(this.client.getHabbo(), true);
@@ -41,7 +73,28 @@ public class RequestRoomLoadEvent extends MessageHandler {
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);
}
}
}
@@ -555,4 +555,10 @@ public class Outgoing {
public static final int SnowStormUserRematchedComposer = 5029;
// Custom Prefixes
public static final int UserPrefixesComposer = 7001;
public static final int PrefixReceivedComposer = 7002;
public static final int ActivePrefixUpdatedComposer = 7003;
public static final int AvailableCommandsComposer = 4050;
}
@@ -0,0 +1,32 @@
package com.eu.habbo.messages.outgoing.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.commands.Command;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
import java.util.List;
public class AvailableCommandsComposer extends MessageComposer {
private final List<Command> commands;
public AvailableCommandsComposer(List<Command> commands) {
this.commands = commands;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.AvailableCommandsComposer);
this.response.appendInt(this.commands.size());
for (Command cmd : this.commands) {
this.response.appendString(cmd.keys[0]);
this.response.appendString(
Emulator.getTexts().getValue("commands.description." + cmd.permission, cmd.permission)
);
}
return this.response;
}
}
@@ -0,0 +1,35 @@
package com.eu.habbo.messages.outgoing.inventory.prefixes;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class ActivePrefixUpdatedComposer extends MessageComposer {
private final UserPrefix prefix;
public ActivePrefixUpdatedComposer(UserPrefix prefix) {
this.prefix = prefix;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.ActivePrefixUpdatedComposer);
if (this.prefix != null) {
this.response.appendInt(this.prefix.getId());
this.response.appendString(this.prefix.getText());
this.response.appendString(this.prefix.getColor());
this.response.appendString(this.prefix.getIcon());
this.response.appendString(this.prefix.getEffect());
} else {
this.response.appendInt(0);
this.response.appendString("");
this.response.appendString("");
this.response.appendString("");
this.response.appendString("");
}
return this.response;
}
}
@@ -0,0 +1,25 @@
package com.eu.habbo.messages.outgoing.inventory.prefixes;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class PrefixReceivedComposer extends MessageComposer {
private final UserPrefix prefix;
public PrefixReceivedComposer(UserPrefix prefix) {
this.prefix = prefix;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.PrefixReceivedComposer);
this.response.appendInt(this.prefix.getId());
this.response.appendString(this.prefix.getText());
this.response.appendString(this.prefix.getColor());
this.response.appendString(this.prefix.getIcon());
this.response.appendString(this.prefix.getEffect());
return this.response;
}
}
@@ -0,0 +1,38 @@
package com.eu.habbo.messages.outgoing.inventory.prefixes;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
import java.util.List;
public class UserPrefixesComposer extends MessageComposer {
private final Habbo habbo;
public UserPrefixesComposer(Habbo habbo) {
this.habbo = habbo;
}
@Override
protected ServerMessage composeInternal() {
if (this.habbo == null) return null;
List<UserPrefix> prefixes = this.habbo.getInventory().getPrefixesComponent().getPrefixes();
this.response.init(Outgoing.UserPrefixesComposer);
this.response.appendInt(prefixes.size());
for (UserPrefix prefix : prefixes) {
this.response.appendInt(prefix.getId());
this.response.appendString(prefix.getText());
this.response.appendString(prefix.getColor());
this.response.appendString(prefix.getIcon());
this.response.appendString(prefix.getEffect());
this.response.appendInt(prefix.isActive() ? 1 : 0);
}
return this.response;
}
}