From a1749c9eda3ee11db80a62d7142c5c6457b98102 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 24 May 2026 11:24:45 +0200 Subject: [PATCH] =?UTF-8?q?feat(housekeeping):=20rooms=20domain=20?= =?UTF-8?q?=E2=80=94=20find/search=20+=20open/close/mute/kick-all/transfer?= =?UTF-8?q?/delete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight new incoming handlers + two new outgoing composers cover the full rooms-domain HK panel. * Outgoing 9202 HousekeepingRoomDetailComposer — single room with a leading `found` boolean. Writes the IHousekeepingRoom shape via a static `appendRoomFields` that HousekeepingRoomListComposer shares. * Outgoing 9203 HousekeepingRoomListComposer — `count` then N rooms. Used for both find-by-name (exact match, up to 50) and the prefix autocomplete dropdown (up to 8). * Incoming 9110 HousekeepingFindRoomByIdEvent — loadRoom(id, false) covers both the in-memory cache and the offline `SELECT * FROM rooms` path. No `loadData` so HK doesn't pull furni/bots/pets just to render a summary. * Incoming 9111 HousekeepingSearchRoomsEvent — (query, exactMatch, limit). Branches between `name = ?` and `name LIKE ?` so the same wire packet serves both the autocomplete and the exact-find flows. Hard-capped to 50. * Incoming 9112 HousekeepingRoomStateEvent — (roomId, open). Toggles Room.setState(OPEN | LOCKED) and persists via Room.save(). One packet covers both the open and close API endpoints. * Incoming 9113 HousekeepingMuteRoomEvent — (roomId, minutes). Room. setMuted is a boolean, so minutes==0 unmutes and minutes>0 mutes. A scheduled auto-unmute is left for a future slice; the wire field is reserved. * Incoming 9114 HousekeepingKickAllFromRoomEvent — Room.ejectAll(). * Incoming 9115 HousekeepingTransferRoomOwnershipEvent — UPDATEs both rooms.owner_id and rooms.owner_name so the navigator cached name doesn't go stale. Validates the new owner exists via HabboManager.getHabboInfo before touching the row. * Incoming 9116 HousekeepingDeleteRoomEvent — ejectAll + dispose + uncacheRoom + DELETE FROM rooms, mirroring the minimum-viable subset of RequestDeleteRoomEvent. Pets/guild/custom-layout cleanup is skipped on this slice (orphans don't crash the emulator). `mvn compile` clean. --- .../com/eu/habbo/messages/PacketManager.java | 7 ++ .../eu/habbo/messages/incoming/Incoming.java | 7 ++ .../HousekeepingDeleteRoomEvent.java | 68 +++++++++++++++++ .../HousekeepingFindRoomByIdEvent.java | 37 +++++++++ .../HousekeepingKickAllFromRoomEvent.java | 41 ++++++++++ .../HousekeepingMuteRoomEvent.java | 50 +++++++++++++ .../HousekeepingRoomStateEvent.java | 49 ++++++++++++ .../HousekeepingSearchRoomsEvent.java | 75 +++++++++++++++++++ ...ousekeepingTransferRoomOwnershipEvent.java | 67 +++++++++++++++++ .../eu/habbo/messages/outgoing/Outgoing.java | 2 + .../HousekeepingRoomDetailComposer.java | 49 ++++++++++++ .../HousekeepingRoomListComposer.java | 30 ++++++++ 12 files changed, 482 insertions(+) create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingFindRoomByIdEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSearchRoomsEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomDetailComposer.java create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomListComposer.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java index 00f69eb4..564a8a7a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java @@ -728,5 +728,12 @@ public class PacketManager { this.registerHandler(Incoming.HousekeepingSetUserRankEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSetUserRankEvent.class); this.registerHandler(Incoming.HousekeepingTradeLockUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTradeLockUserEvent.class); this.registerHandler(Incoming.HousekeepingResetUserPasswordEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingResetUserPasswordEvent.class); + this.registerHandler(Incoming.HousekeepingFindRoomByIdEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindRoomByIdEvent.class); + this.registerHandler(Incoming.HousekeepingSearchRoomsEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSearchRoomsEvent.class); + this.registerHandler(Incoming.HousekeepingRoomStateEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingRoomStateEvent.class); + this.registerHandler(Incoming.HousekeepingMuteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingMuteRoomEvent.class); + this.registerHandler(Incoming.HousekeepingKickAllFromRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingKickAllFromRoomEvent.class); + this.registerHandler(Incoming.HousekeepingTransferRoomOwnershipEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTransferRoomOwnershipEvent.class); + this.registerHandler(Incoming.HousekeepingDeleteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingDeleteRoomEvent.class); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java index 5f4a1a58..aeb4a766 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java @@ -472,4 +472,11 @@ public class Incoming { public static final int HousekeepingSetUserRankEvent = 9107; public static final int HousekeepingTradeLockUserEvent = 9108; public static final int HousekeepingResetUserPasswordEvent = 9109; + public static final int HousekeepingFindRoomByIdEvent = 9110; + public static final int HousekeepingSearchRoomsEvent = 9111; + public static final int HousekeepingRoomStateEvent = 9112; + public static final int HousekeepingMuteRoomEvent = 9113; + public static final int HousekeepingKickAllFromRoomEvent = 9114; + public static final int HousekeepingTransferRoomOwnershipEvent = 9115; + public static final int HousekeepingDeleteRoomEvent = 9116; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java new file mode 100644 index 00000000..9e85deb5 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingDeleteRoomEvent.java @@ -0,0 +1,68 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Permanently delete a room. Mirrors the minimum-viable subset of + * RequestDeleteRoomEvent: eject all users from the live room, dispose + * + uncache, then DELETE FROM rooms. Pets/guild/custom-layout cleanup + * is intentionally skipped on this slice — leftover rows in those + * tables become orphans but don't crash the emulator; a follow-up + * pass can cascade once we have a HK audit-log row to attach the + * orphan-cleanup to. + */ +public class HousekeepingDeleteRoomEvent extends MessageHandler { + private static final String ACTION_KEY = "room.delete"; + + @Override + public int getRatelimit() { + return 2000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int roomId = this.packet.readInt(); + + if (roomId <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); + + if (room != null) { + room.ejectAll(); + room.preventUnloading = false; + room.dispose(); + Emulator.getGameEnvironment().getRoomManager().uncacheRoom(room); + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("DELETE FROM rooms WHERE id = ? LIMIT 1")) { + statement.setInt(1, roomId); + int rows = statement.executeUpdate(); + + if (rows == 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found")); + return; + } + } catch (SQLException e) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed")); + return; + } + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingFindRoomByIdEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingFindRoomByIdEvent.java new file mode 100644 index 00000000..dc9267c4 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingFindRoomByIdEvent.java @@ -0,0 +1,37 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingRoomDetailComposer; + +public class HousekeepingFindRoomByIdEvent extends MessageHandler { + @Override + public int getRatelimit() { + return 500; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int roomId = this.packet.readInt(); + + if (roomId <= 0) { + this.client.sendResponse(new HousekeepingRoomDetailComposer(null)); + return; + } + + // loadRoom covers both the in-memory cache (already-loaded rooms) + // and the offline path (SELECT * FROM rooms WHERE id=?). Pass + // false for loadData so we don't pull furni/bots/pets just to + // render an HK panel summary — getOwnerName / getUserCount work + // on the pre-loaded skeleton. + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); + + this.client.sendResponse(new HousekeepingRoomDetailComposer(room)); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java new file mode 100644 index 00000000..76ae8e82 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingKickAllFromRoomEvent.java @@ -0,0 +1,41 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +public class HousekeepingKickAllFromRoomEvent extends MessageHandler { + private static final String ACTION_KEY = "room.kick_all"; + + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int roomId = this.packet.readInt(); + + if (roomId <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); + + if (room == null) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found")); + return; + } + + room.ejectAll(); + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java new file mode 100644 index 00000000..b471affc --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingMuteRoomEvent.java @@ -0,0 +1,50 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +/** + * Toggle room-wide mute. Habbo's Room.setMuted is boolean, not duration- + * scoped, so the wire `minutes` arg picks the semantic: minutes==0 => + * unmute, minutes>0 => mute. An emulator-side scheduled unmute could + * use the value as a timer, but for now the mute stays until the + * operator unmutes manually — the minutes is reserved as a forward- + * compat field on the wire. + */ +public class HousekeepingMuteRoomEvent extends MessageHandler { + private static final String ACTION_KEY = "room.mute"; + + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int roomId = this.packet.readInt(); + int minutes = this.packet.readInt(); + + if (roomId <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); + + if (room == null) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found")); + return; + } + + room.setMuted(minutes > 0); + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java new file mode 100644 index 00000000..ea902db8 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingRoomStateEvent.java @@ -0,0 +1,49 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomState; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +/** + * Toggle the room state between OPEN (open) and LOCKED (closed). The + * client picks which transition it wants via the boolean — true => OPEN, + * false => LOCKED. Persists state through `Room.save()` so the change + * outlives an unload. + */ +public class HousekeepingRoomStateEvent extends MessageHandler { + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int roomId = this.packet.readInt(); + boolean open = this.packet.readBoolean(); + String actionKey = open ? "room.open" : "room.close"; + + if (roomId <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.invalid_input")); + return; + } + + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false); + + if (room == null) { + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.room_not_found")); + return; + } + + room.setState(open ? RoomState.OPEN : RoomState.LOCKED); + room.save(); + + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, roomId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSearchRoomsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSearchRoomsEvent.java new file mode 100644 index 00000000..985250c9 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSearchRoomsEvent.java @@ -0,0 +1,75 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingRoomListComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Search rooms by name. `exactMatch=true` => `name = ?` (used by the + * findByName autocomplete that wants a unique hit). `exactMatch=false` + * => `name LIKE concat(?, '%')` (used by the prefix dropdown). + * + * Both branches go through the same packet because the wire shape is + * identical — the client picks which mode it wants by toggling the + * boolean. + */ +public class HousekeepingSearchRoomsEvent extends MessageHandler { + private static final int HARD_LIMIT = 50; + + @Override + public int getRatelimit() { + return 500; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + String query = this.packet.readString(); + boolean exactMatch = this.packet.readBoolean(); + int limit = Math.min(Math.max(this.packet.readInt(), 1), HARD_LIMIT); + + if (query == null) query = ""; + query = query.trim(); + + if (query.isEmpty()) { + this.client.sendResponse(new HousekeepingRoomListComposer(new ArrayList<>())); + return; + } + + String sql = exactMatch + ? "SELECT id FROM rooms WHERE name = ? LIMIT ?" + : "SELECT id FROM rooms WHERE name LIKE ? LIMIT ?"; + + List rooms = new ArrayList<>(); + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, exactMatch ? query : query + "%"); + statement.setInt(2, limit); + + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(set.getInt("id"), false); + + if (room != null) rooms.add(room); + } + } + } catch (SQLException ignored) { + // fall through with whatever we collected before the failure + } + + this.client.sendResponse(new HousekeepingRoomListComposer(rooms)); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java new file mode 100644 index 00000000..70c6bf80 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingTransferRoomOwnershipEvent.java @@ -0,0 +1,67 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.users.HabboInfo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Transfer ownership of a room to a different user. Updates both + * `rooms.owner_id` and `rooms.owner_name` so the cached owner name on + * the navigator stays in sync without forcing a relog. The room is + * touched via direct SQL rather than via Room.setOwnerId() because + * the room may not be loaded. + */ +public class HousekeepingTransferRoomOwnershipEvent extends MessageHandler { + private static final String ACTION_KEY = "room.transfer"; + + @Override + public int getRatelimit() { + return 2000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int roomId = this.packet.readInt(); + int newOwnerId = this.packet.readInt(); + + if (roomId <= 0 || newOwnerId <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + HabboInfo newOwner = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(newOwnerId); + + if (newOwner == null) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.new_owner_not_found")); + return; + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET owner_id = ?, owner_name = ? WHERE id = ? LIMIT 1")) { + statement.setInt(1, newOwnerId); + statement.setString(2, newOwner.getUsername()); + statement.setInt(3, roomId); + int rows = statement.executeUpdate(); + + if (rows == 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found")); + return; + } + } catch (SQLException e) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed")); + return; + } + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java index aee805e8..0904698a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java @@ -589,5 +589,7 @@ public class Outgoing { // Housekeeping (in-client admin panel) — IDs 9200..9299 reserved public static final int HousekeepingUserDetailComposer = 9200; public static final int HousekeepingActionResultComposer = 9201; + public static final int HousekeepingRoomDetailComposer = 9202; + public static final int HousekeepingRoomListComposer = 9203; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomDetailComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomDetailComposer.java new file mode 100644 index 00000000..b3bd4285 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomDetailComposer.java @@ -0,0 +1,49 @@ +package com.eu.habbo.messages.outgoing.housekeeping; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomState; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +public class HousekeepingRoomDetailComposer extends MessageComposer { + private final Room room; + + public HousekeepingRoomDetailComposer(Room room) { + this.room = room; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.HousekeepingRoomDetailComposer); + + if (this.room == null) { + this.response.appendBoolean(false); + return this.response; + } + + this.response.appendBoolean(true); + appendRoomFields(this.response, this.room); + + return this.response; + } + + /** Shared by HousekeepingRoomListComposer too. */ + public static void appendRoomFields(ServerMessage response, Room room) { + response.appendInt(room.getId()); + response.appendString(safe(room.getName())); + response.appendString(safe(room.getDescription())); + response.appendInt(room.getOwnerId()); + response.appendString(safe(room.getOwnerName())); + response.appendInt(room.getUserCount()); + response.appendInt(room.getUsersMax()); + response.appendBoolean(room.getState() != null && room.getState() != RoomState.OPEN); + response.appendBoolean(room.isMuted()); + response.appendBoolean(room.isPublicRoom()); + response.appendInt(0); // createdAt — Room doesn't expose; left as 0 until a schema-side timestamp surfaces. + } + + private static String safe(String value) { + return value != null ? value : ""; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomListComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomListComposer.java new file mode 100644 index 00000000..7fc6b81f --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/housekeeping/HousekeepingRoomListComposer.java @@ -0,0 +1,30 @@ +package com.eu.habbo.messages.outgoing.housekeeping; + +import com.eu.habbo.habbohotel.rooms.Room; +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 HousekeepingRoomListComposer extends MessageComposer { + private final List rooms; + + public HousekeepingRoomListComposer(List rooms) { + this.rooms = rooms; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.HousekeepingRoomListComposer); + this.response.appendInt(this.rooms != null ? this.rooms.size() : 0); + + if (this.rooms != null) { + for (Room room : this.rooms) { + HousekeepingRoomDetailComposer.appendRoomFields(this.response, room); + } + } + + return this.response; + } +}