feat: CatalogAdmin server handlers (10050-10059) with acc_catalogfurni

Add 9 incoming packet handlers + 1 outgoing composer for catalog
admin management via websocket. All operations require acc_catalogfurni
permission.

Handlers:
- CatalogAdminSavePageEvent (10050) - UPDATE catalog_pages
- CatalogAdminCreatePageEvent (10051) - INSERT catalog_pages
- CatalogAdminDeletePageEvent (10052) - DELETE catalog_pages
- CatalogAdminSaveOfferEvent (10053) - UPDATE catalog_items
- CatalogAdminCreateOfferEvent (10054) - INSERT catalog_items
- CatalogAdminDeleteOfferEvent (10055) - DELETE catalog_items
- CatalogAdminMoveOfferEvent (10056) - reorder offers
- CatalogAdminMovePageEvent (10057) - move/toggle enabled/visible
- CatalogAdminPublishEvent (10058) - reload catalog + broadcast
- CatalogAdminResultComposer (10059) - response (success + message)

Includes:
- New permission constant ACC_CATALOGFURNI in Permission.java
- SQL migration to add acc_catalogfurni column to permissions table
- Enabled by default for Administrator rank (id=7)
This commit is contained in:
Life
2026-03-22 16:10:41 +01:00
parent dc930c086e
commit 4a20f26685
15 changed files with 493 additions and 0 deletions
@@ -0,0 +1,17 @@
-- ============================================================
-- Catalog & Furni Admin Permission
-- Adds acc_catalogfurni permission to the permissions table
-- Required by: CatalogAdmin packet handlers (10050-10059)
-- ============================================================
-- 1. Add the column to the permissions table
ALTER TABLE `permissions`
ADD COLUMN `acc_catalogfurni` ENUM('0','1') NOT NULL DEFAULT '0'
AFTER `acc_catalog_ids`;
-- 2. Enable for Administrator (rank 7) by default
UPDATE `permissions` SET `acc_catalogfurni` = '1' WHERE `id` = 7;
-- Optional: enable for other ranks as needed
-- UPDATE `permissions` SET `acc_catalogfurni` = '1' WHERE `id` = 6; -- Super Mod
-- UPDATE `permissions` SET `acc_catalogfurni` = '1' WHERE `id` = 5; -- Moderator
@@ -31,6 +31,7 @@ public class Permission {
public static String ACC_NOMUTE = "acc_nomute";
public static String ACC_GUILD_ADMIN = "acc_guild_admin";
public static String ACC_CATALOG_IDS = "acc_catalog_ids";
public static String ACC_CATALOGFURNI = "acc_catalogfurni";
public static String ACC_MODTOOL_TICKET_Q = "acc_modtool_ticket_q";
public static String ACC_MODTOOL_USER_LOGS = "acc_modtool_user_logs";
public static String ACC_MODTOOL_USER_ALERT = "acc_modtool_user_alert";
@@ -10,6 +10,7 @@ import com.eu.habbo.messages.incoming.ambassadors.AmbassadorAlertCommandEvent;
import com.eu.habbo.messages.incoming.ambassadors.AmbassadorVisitCommandEvent;
import com.eu.habbo.messages.incoming.camera.*;
import com.eu.habbo.messages.incoming.catalog.*;
import com.eu.habbo.messages.incoming.catalog.catalogadmin.*;
import com.eu.habbo.messages.incoming.catalog.marketplace.*;
import com.eu.habbo.messages.incoming.catalog.recycler.OpenRecycleBoxEvent;
import com.eu.habbo.messages.incoming.catalog.recycler.RecycleEvent;
@@ -258,6 +259,17 @@ public class PacketManager {
this.registerHandler(Incoming.RequestClubCenterEvent, RequestClubCenterEvent.class);
this.registerHandler(Incoming.CatalogRequestClubDiscountEvent, CatalogRequestClubDiscountEvent.class);
this.registerHandler(Incoming.CatalogBuyClubDiscountEvent, CatalogBuyClubDiscountEvent.class);
// Catalog Admin
this.registerHandler(Incoming.CatalogAdminSavePageEvent, CatalogAdminSavePageEvent.class);
this.registerHandler(Incoming.CatalogAdminCreatePageEvent, CatalogAdminCreatePageEvent.class);
this.registerHandler(Incoming.CatalogAdminDeletePageEvent, CatalogAdminDeletePageEvent.class);
this.registerHandler(Incoming.CatalogAdminSaveOfferEvent, CatalogAdminSaveOfferEvent.class);
this.registerHandler(Incoming.CatalogAdminCreateOfferEvent, CatalogAdminCreateOfferEvent.class);
this.registerHandler(Incoming.CatalogAdminDeleteOfferEvent, CatalogAdminDeleteOfferEvent.class);
this.registerHandler(Incoming.CatalogAdminMoveOfferEvent, CatalogAdminMoveOfferEvent.class);
this.registerHandler(Incoming.CatalogAdminMovePageEvent, CatalogAdminMovePageEvent.class);
this.registerHandler(Incoming.CatalogAdminPublishEvent, CatalogAdminPublishEvent.class);
}
private void registerEvent() throws Exception {
@@ -412,6 +412,17 @@ public class Incoming {
public static final int RequestInventoryPetDelete = 10030;
public static final int RequestInventoryBadgeDelete = 10031;
// Catalog Admin
public static final int CatalogAdminSavePageEvent = 10050;
public static final int CatalogAdminCreatePageEvent = 10051;
public static final int CatalogAdminDeletePageEvent = 10052;
public static final int CatalogAdminSaveOfferEvent = 10053;
public static final int CatalogAdminCreateOfferEvent = 10054;
public static final int CatalogAdminDeleteOfferEvent = 10055;
public static final int CatalogAdminMoveOfferEvent = 10056;
public static final int CatalogAdminMovePageEvent = 10057;
public static final int CatalogAdminPublishEvent = 10058;
// Custom Prefixes
public static final int RequestUserPrefixesEvent = 7011;
public static final int SetActivePrefixEvent = 7012;
@@ -0,0 +1,70 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
public class CatalogAdminCreateOfferEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int pageId = this.packet.readInt();
int itemId = this.packet.readInt();
String catalogName = this.packet.readString();
int costCredits = this.packet.readInt();
int costPoints = this.packet.readInt();
int pointsType = this.packet.readInt();
int amount = this.packet.readInt();
int clubOnly = this.packet.readInt();
String extradata = this.packet.readString();
boolean haveOffer = this.packet.readBoolean();
int offerIdGroup = this.packet.readInt();
int limitedStack = this.packet.readInt();
int orderNumber = this.packet.readInt();
int newId = -1;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO catalog_items (page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, club_only, extradata, have_offer, offer_id, limited_stack, order_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, pageId);
statement.setString(2, String.valueOf(itemId));
statement.setString(3, catalogName);
statement.setInt(4, costCredits);
statement.setInt(5, costPoints);
statement.setInt(6, pointsType);
statement.setInt(7, amount);
statement.setString(8, clubOnly == 1 ? "1" : "0");
statement.setString(9, extradata);
statement.setString(10, haveOffer ? "1" : "0");
statement.setInt(11, offerIdGroup);
statement.setInt(12, limitedStack);
statement.setInt(13, orderNumber);
statement.execute();
try (ResultSet keys = statement.getGeneratedKeys()) {
if (keys.next()) {
newId = keys.getInt(1);
}
}
}
if (newId > 0) {
this.client.sendResponse(new CatalogAdminResultComposer(true, "Offer created: " + newId));
} else {
this.client.sendResponse(new CatalogAdminResultComposer(false, "Failed to create offer"));
}
}
}
@@ -0,0 +1,47 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.habbohotel.catalog.CatalogPageLayouts;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
public class CatalogAdminCreatePageEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
String caption = this.packet.readString();
String caption2 = this.packet.readString();
String layout = this.packet.readString();
int iconType = this.packet.readInt();
int minRank = this.packet.readInt();
boolean visible = this.packet.readBoolean();
boolean enabled = this.packet.readBoolean();
int orderNum = this.packet.readInt();
int parentId = this.packet.readInt();
CatalogPageLayouts pageLayout;
try {
pageLayout = CatalogPageLayouts.valueOf(layout);
} catch (IllegalArgumentException e) {
pageLayout = CatalogPageLayouts.default_3x3;
}
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().createCatalogPage(
caption, caption2, 0, iconType, pageLayout, minRank, parentId
);
if (page == null) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "Failed to create page"));
return;
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page created: " + page.getId()));
}
}
@@ -0,0 +1,30 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class CatalogAdminDeleteOfferEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int offerId = this.packet.readInt();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM catalog_items WHERE id = ?")) {
statement.setInt(1, offerId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Offer deleted"));
}
}
@@ -0,0 +1,40 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class CatalogAdminDeletePageEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int pageId = this.packet.readInt();
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
if (page == null) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "Page not found: " + pageId));
return;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM catalog_pages WHERE id = ?")) {
statement.setInt(1, pageId);
statement.execute();
}
Emulator.getGameEnvironment().getCatalogManager().catalogPages.remove(pageId);
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page deleted"));
}
}
@@ -0,0 +1,32 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class CatalogAdminMoveOfferEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int offerId = this.packet.readInt();
int orderNumber = this.packet.readInt();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("UPDATE catalog_items SET order_number = ? WHERE id = ?")) {
statement.setInt(1, orderNumber);
statement.setInt(2, offerId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Offer reordered"));
}
}
@@ -0,0 +1,61 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class CatalogAdminMovePageEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int pageId = this.packet.readInt();
int newParentId = this.packet.readInt();
int newIndex = this.packet.readInt();
// Special values: -1 = toggle enabled, -2 = toggle visible
if (newParentId == -1) {
// Toggle enabled
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE catalog_pages SET enabled = IF(enabled = '1', '0', '1') WHERE id = ?")) {
statement.setInt(1, pageId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page toggled"));
return;
}
if (newParentId == -2) {
// Toggle visible
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE catalog_pages SET visible = IF(visible = '1', '0', '1') WHERE id = ?")) {
statement.setInt(1, pageId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Visibility toggled"));
return;
}
// Normal move: update parent and order
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE catalog_pages SET parent_id = ?, order_num = ? WHERE id = ?")) {
statement.setInt(1, newParentId);
statement.setInt(2, newIndex);
statement.setInt(3, pageId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page moved"));
}
}
@@ -0,0 +1,26 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.CatalogUpdatedComposer;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
public class CatalogAdminPublishEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
// Reload the entire catalog from database
Emulator.getGameEnvironment().getCatalogManager().initialize();
// Notify all connected clients that the catalog has been updated
Emulator.getGameServer().getGameClientManager().sendBroadcastResponse(new CatalogUpdatedComposer());
this.client.sendResponse(new CatalogAdminResultComposer(true, "Catalog published"));
}
}
@@ -0,0 +1,57 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class CatalogAdminSaveOfferEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int offerId = this.packet.readInt();
int pageId = this.packet.readInt();
int itemId = this.packet.readInt();
String catalogName = this.packet.readString();
int costCredits = this.packet.readInt();
int costPoints = this.packet.readInt();
int pointsType = this.packet.readInt();
int amount = this.packet.readInt();
int clubOnly = this.packet.readInt();
String extradata = this.packet.readString();
boolean haveOffer = this.packet.readBoolean();
int offerIdGroup = this.packet.readInt();
int limitedStack = this.packet.readInt();
int orderNumber = this.packet.readInt();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE catalog_items SET page_id = ?, item_ids = ?, catalog_name = ?, cost_credits = ?, cost_points = ?, points_type = ?, amount = ?, club_only = ?, extradata = ?, have_offer = ?, offer_id = ?, limited_stack = ?, order_number = ? WHERE id = ?")) {
statement.setInt(1, pageId);
statement.setString(2, String.valueOf(itemId));
statement.setString(3, catalogName);
statement.setInt(4, costCredits);
statement.setInt(5, costPoints);
statement.setInt(6, pointsType);
statement.setInt(7, amount);
statement.setString(8, clubOnly == 1 ? "1" : "0");
statement.setString(9, extradata);
statement.setString(10, haveOffer ? "1" : "0");
statement.setInt(11, offerIdGroup);
statement.setInt(12, limitedStack);
statement.setInt(13, orderNumber);
statement.setInt(14, offerId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Offer saved"));
}
}
@@ -0,0 +1,63 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class CatalogAdminSavePageEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int pageId = this.packet.readInt();
String caption = this.packet.readString();
String caption2 = this.packet.readString();
String layout = this.packet.readString();
int iconType = this.packet.readInt();
int minRank = this.packet.readInt();
boolean visible = this.packet.readBoolean();
boolean enabled = this.packet.readBoolean();
int orderNum = this.packet.readInt();
int parentId = this.packet.readInt();
String headline = this.packet.readString();
String teaser = this.packet.readString();
String textDetails = this.packet.readString();
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
if (page == null) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "Page not found: " + pageId));
return;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE catalog_pages SET caption = ?, caption_save = ?, page_layout = ?, icon_image = ?, min_rank = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ? WHERE id = ?")) {
statement.setString(1, caption);
statement.setString(2, caption2);
statement.setString(3, layout);
statement.setInt(4, iconType);
statement.setInt(5, minRank);
statement.setString(6, visible ? "1" : "0");
statement.setString(7, enabled ? "1" : "0");
statement.setInt(8, orderNum);
statement.setInt(9, parentId);
statement.setString(10, headline);
statement.setString(11, teaser);
statement.setString(12, textDetails);
statement.setInt(13, pageId);
statement.execute();
}
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page saved"));
}
}
@@ -555,6 +555,9 @@ public class Outgoing {
public static final int SnowStormUserRematchedComposer = 5029;
// Catalog Admin
public static final int CatalogAdminResultComposer = 10059;
// Custom Prefixes
public static final int UserPrefixesComposer = 7001;
public static final int PrefixReceivedComposer = 7002;
@@ -0,0 +1,23 @@
package com.eu.habbo.messages.outgoing.catalog.catalogadmin;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class CatalogAdminResultComposer extends MessageComposer {
private final boolean success;
private final String message;
public CatalogAdminResultComposer(boolean success, String message) {
this.success = success;
this.message = message;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.CatalogAdminResultComposer);
this.response.appendBoolean(this.success);
this.response.appendString(this.message);
return this.response;
}
}