You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
Merge pull request #53 from simoleo89/furnieditor
feat: FurniEditor WebSocket (packets 10040-10045)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
-- FurniEditor: emulator config keys for FurniDataManager
|
||||
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('furni.editor.renderer.config.path', 'E:/www/habbo-next/public/nitro3/public/renderer-config.json'),
|
||||
('furni.editor.asset.base.path', 'E:/www/habbo-next/public/nitro-assets/');
|
||||
@@ -11,6 +11,7 @@ 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.furnieditor.*;
|
||||
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;
|
||||
@@ -260,6 +261,14 @@ public class PacketManager {
|
||||
this.registerHandler(Incoming.CatalogRequestClubDiscountEvent, CatalogRequestClubDiscountEvent.class);
|
||||
this.registerHandler(Incoming.CatalogBuyClubDiscountEvent, CatalogBuyClubDiscountEvent.class);
|
||||
|
||||
// Furni Editor
|
||||
this.registerHandler(Incoming.FurniEditorSearchEvent, FurniEditorSearchEvent.class);
|
||||
this.registerHandler(Incoming.FurniEditorDetailEvent, FurniEditorDetailEvent.class);
|
||||
this.registerHandler(Incoming.FurniEditorBySpriteEvent, FurniEditorBySpriteEvent.class);
|
||||
this.registerHandler(Incoming.FurniEditorInteractionsEvent, FurniEditorInteractionsEvent.class);
|
||||
this.registerHandler(Incoming.FurniEditorUpdateEvent, FurniEditorUpdateEvent.class);
|
||||
this.registerHandler(Incoming.FurniEditorDeleteEvent, FurniEditorDeleteEvent.class);
|
||||
|
||||
// Catalog Admin
|
||||
this.registerHandler(Incoming.CatalogAdminSavePageEvent, CatalogAdminSavePageEvent.class);
|
||||
this.registerHandler(Incoming.CatalogAdminCreatePageEvent, CatalogAdminCreatePageEvent.class);
|
||||
|
||||
@@ -412,6 +412,14 @@ public class Incoming {
|
||||
public static final int RequestInventoryPetDelete = 10030;
|
||||
public static final int RequestInventoryBadgeDelete = 10031;
|
||||
|
||||
// Furni Editor
|
||||
public static final int FurniEditorSearchEvent = 10040;
|
||||
public static final int FurniEditorDetailEvent = 10041;
|
||||
public static final int FurniEditorBySpriteEvent = 10042;
|
||||
public static final int FurniEditorInteractionsEvent = 10043;
|
||||
public static final int FurniEditorUpdateEvent = 10044;
|
||||
public static final int FurniEditorDeleteEvent = 10045;
|
||||
|
||||
// Catalog Admin
|
||||
public static final int CatalogAdminSavePageEvent = 10050;
|
||||
public static final int CatalogAdminCreatePageEvent = 10051;
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Manages reading and writing of FurnitureData.json entries.
|
||||
* Resolves the file path from emulator config keys.
|
||||
*/
|
||||
public class FurniDataManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FurniDataManager.class);
|
||||
|
||||
/**
|
||||
* Get the JSON string for a specific item from FurnitureData.json.
|
||||
* Returns "{}" if not found or on error.
|
||||
*/
|
||||
public static String getItemJson(int itemId) {
|
||||
try {
|
||||
Path furniDataPath = resolveFurniDataPath();
|
||||
if (furniDataPath == null || !Files.exists(furniDataPath)) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
String content = Files.readString(furniDataPath, StandardCharsets.UTF_8);
|
||||
JsonObject root = JsonParser.parseString(content).getAsJsonObject();
|
||||
|
||||
// Search in both "roomitemtypes" and "wallitemtypes"
|
||||
for (String section : new String[]{"roomitemtypes", "wallitemtypes"}) {
|
||||
if (!root.has(section)) continue;
|
||||
JsonObject sectionObj = root.getAsJsonObject(section);
|
||||
if (!sectionObj.has("furnitype")) continue;
|
||||
JsonArray types = sectionObj.getAsJsonArray("furnitype");
|
||||
|
||||
for (JsonElement el : types) {
|
||||
JsonObject obj = el.getAsJsonObject();
|
||||
if (obj.has("id") && obj.get("id").getAsInt() == itemId) {
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to read FurnitureData.json for item " + itemId, e);
|
||||
}
|
||||
|
||||
return "{}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the path to FurnitureData.json from emulator config.
|
||||
*/
|
||||
private static Path resolveFurniDataPath() {
|
||||
try {
|
||||
String configPath = Emulator.getConfig().getValue("furni.editor.renderer.config.path", "");
|
||||
|
||||
if (configPath.isEmpty()) {
|
||||
// Fallback: try common locations
|
||||
String basePath = Emulator.getConfig().getValue("furni.editor.asset.base.path", "");
|
||||
if (!basePath.isEmpty()) {
|
||||
Path candidate = Paths.get(basePath, "FurnitureData.json");
|
||||
if (Files.exists(candidate)) return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the renderer config to find the furnidata URL/path
|
||||
Path rendererConfig = Paths.get(configPath);
|
||||
if (!Files.exists(rendererConfig)) return null;
|
||||
|
||||
String rendererContent = Files.readString(rendererConfig, StandardCharsets.UTF_8);
|
||||
JsonObject rendererObj = JsonParser.parseString(rendererContent).getAsJsonObject();
|
||||
|
||||
if (rendererObj.has("furnidata.url")) {
|
||||
String furniUrl = rendererObj.get("furnidata.url").getAsString();
|
||||
|
||||
// Skip unresolved placeholders like ${gamedata.url}
|
||||
if (furniUrl.contains("${")) {
|
||||
String basePath = Emulator.getConfig().getValue("furni.editor.asset.base.path", "");
|
||||
if (!basePath.isEmpty()) {
|
||||
Path candidate = Paths.get(basePath, "FurnitureData.json");
|
||||
if (Files.exists(candidate)) return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Strip query string (?v=1 etc.)
|
||||
String cleanUrl = furniUrl.contains("?") ? furniUrl.substring(0, furniUrl.indexOf('?')) : furniUrl;
|
||||
|
||||
// If it's a local file path (not http), use it directly
|
||||
if (!cleanUrl.startsWith("http")) {
|
||||
return Paths.get(cleanUrl);
|
||||
}
|
||||
|
||||
// For http URLs, try to derive local path from base path
|
||||
String basePath = Emulator.getConfig().getValue("furni.editor.asset.base.path", "");
|
||||
if (!basePath.isEmpty()) {
|
||||
// Extract filename from URL (without query string)
|
||||
String filename = cleanUrl.substring(cleanUrl.lastIndexOf('/') + 1);
|
||||
return Paths.get(basePath, filename);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to resolve FurnitureData.json path", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
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.furnieditor.FurniEditorResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
public class FurniEditorBySpriteEvent extends MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
int spriteId = this.packet.readInt();
|
||||
|
||||
if (spriteId <= 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "Invalid sprite ID"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up the item ID by sprite_id
|
||||
int itemId = -1;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement stmt = connection.prepareStatement("SELECT id FROM items_base WHERE sprite_id = ? LIMIT 1")) {
|
||||
stmt.setInt(1, spriteId);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
itemId = rs.getInt("id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId <= 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No item found with sprite_id: " + spriteId));
|
||||
return;
|
||||
}
|
||||
|
||||
// Delegate to the detail response builder
|
||||
FurniEditorDetailEvent.sendDetailResponse(this.client, itemId);
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
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.furnieditor.FurniEditorResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
||||
public class FurniEditorDeleteEvent extends MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = this.packet.readInt();
|
||||
|
||||
if (id <= 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "Invalid item ID"));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
// Check if item exists
|
||||
try (PreparedStatement stmt = connection.prepareStatement("SELECT id FROM items_base WHERE id = ?")) {
|
||||
stmt.setInt(1, id);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "Item not found: " + id));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check usage count - items placed in rooms
|
||||
int usageCount = 0;
|
||||
try (PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) FROM items WHERE item_id = ?")) {
|
||||
stmt.setInt(1, id);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
usageCount = rs.getInt(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (usageCount > 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false,
|
||||
"Cannot delete: " + usageCount + " instances exist in the game"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check catalog_items references
|
||||
int catalogCount = 0;
|
||||
try (PreparedStatement stmt = connection.prepareStatement(
|
||||
"SELECT COUNT(*) FROM catalog_items WHERE item_ids LIKE ?")) {
|
||||
stmt.setString(1, "%" + id + "%");
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
catalogCount = rs.getInt(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (catalogCount > 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false,
|
||||
"Cannot delete: item is referenced by " + catalogCount + " catalog entries"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Safe to delete
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DELETE FROM items_base WHERE id = ?")) {
|
||||
stmt.setInt(1, id);
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// Reload emulator item definitions
|
||||
Emulator.getGameEnvironment().getItemManager().loadItems();
|
||||
|
||||
this.client.sendResponse(new FurniEditorResultComposer(true, "Item deleted"));
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
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.furnieditor.FurniEditorDetailComposer;
|
||||
import com.eu.habbo.messages.outgoing.furnieditor.FurniEditorResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FurniEditorDetailEvent extends MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = this.packet.readInt();
|
||||
|
||||
if (id <= 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "Invalid item ID"));
|
||||
return;
|
||||
}
|
||||
|
||||
sendDetailResponse(this.client, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared method to build and send a detail response for a given item ID.
|
||||
* Used by both FurniEditorDetailEvent and FurniEditorBySpriteEvent.
|
||||
*/
|
||||
public static void sendDetailResponse(com.eu.habbo.habbohotel.gameclients.GameClient client, int itemId) throws Exception {
|
||||
Map<String, Object> item = null;
|
||||
int usageCount = 0;
|
||||
List<Map<String, Object>> catalogItems = new ArrayList<>();
|
||||
String furniDataJson = "{}";
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
// Load full item data
|
||||
try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM items_base WHERE id = ?")) {
|
||||
stmt.setInt(1, itemId);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
item = FurniEditorHelper.readFullItem(rs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
client.sendResponse(new FurniEditorResultComposer(false, "Item not found: " + itemId));
|
||||
return;
|
||||
}
|
||||
|
||||
// Count placed instances
|
||||
try (PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) FROM items WHERE item_id = ?")) {
|
||||
stmt.setInt(1, itemId);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
usageCount = rs.getInt(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load catalog references (join catalog_items with catalog_pages)
|
||||
try (PreparedStatement stmt = connection.prepareStatement(
|
||||
"SELECT ci.id AS ci_id, ci.catalog_name, ci.cost_credits, ci.cost_points, ci.points_type, " +
|
||||
"ci.page_id AS ci_page_id, COALESCE(cp.caption, '') AS page_caption " +
|
||||
"FROM catalog_items ci " +
|
||||
"LEFT JOIN catalog_pages cp ON ci.page_id = cp.id " +
|
||||
"WHERE ci.item_ids LIKE ?")) {
|
||||
stmt.setString(1, "%" + itemId + "%");
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
catalogItems.add(FurniEditorHelper.readCatalogRef(rs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to read furnidata.json entry
|
||||
try {
|
||||
furniDataJson = FurniDataManager.getItemJson(itemId);
|
||||
} catch (Exception e) {
|
||||
furniDataJson = "{}";
|
||||
}
|
||||
|
||||
client.sendResponse(new FurniEditorDetailComposer(item, usageCount, catalogItems, furniDataJson));
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Shared utility for building item data maps from ResultSet rows.
|
||||
* Used by FurniEditorDetailEvent, FurniEditorBySpriteEvent, and
|
||||
* FurniEditorSearchEvent to ensure consistent field reading.
|
||||
*/
|
||||
public class FurniEditorHelper {
|
||||
|
||||
/**
|
||||
* Read the 14 base fields from items_base into a Map.
|
||||
*/
|
||||
public static Map<String, Object> readBaseItem(ResultSet set) throws SQLException {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("id", set.getInt("id"));
|
||||
item.put("sprite_id", set.getInt("sprite_id"));
|
||||
item.put("item_name", set.getString("item_name"));
|
||||
item.put("public_name", set.getString("public_name"));
|
||||
item.put("type", set.getString("type"));
|
||||
item.put("width", set.getInt("width"));
|
||||
item.put("length", set.getInt("length"));
|
||||
item.put("stack_height", set.getDouble("stack_height"));
|
||||
item.put("allow_stack", set.getString("allow_stack"));
|
||||
item.put("allow_walk", set.getString("allow_walk"));
|
||||
item.put("allow_sit", set.getString("allow_sit"));
|
||||
item.put("allow_lay", set.getString("allow_lay"));
|
||||
item.put("interaction_type", set.getString("interaction_type"));
|
||||
item.put("interaction_modes_count", set.getInt("interaction_modes_count"));
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all fields (14 base + 13 extended) from items_base into a Map.
|
||||
*/
|
||||
public static Map<String, Object> readFullItem(ResultSet set) throws SQLException {
|
||||
Map<String, Object> item = readBaseItem(set);
|
||||
item.put("allow_gift", set.getString("allow_gift"));
|
||||
item.put("allow_trade", set.getString("allow_trade"));
|
||||
item.put("allow_recycle", set.getString("allow_recycle"));
|
||||
item.put("allow_marketplace_sell", set.getString("allow_marketplace_sell"));
|
||||
item.put("allow_inventory_stack", set.getString("allow_inventory_stack"));
|
||||
item.put("vending_ids", set.getString("vending_ids"));
|
||||
item.put("customparams", set.getString("customparams"));
|
||||
item.put("effect_id_male", set.getInt("effect_id_male"));
|
||||
item.put("effect_id_female", set.getInt("effect_id_female"));
|
||||
item.put("clothing_on_walk", set.getString("clothing_on_walk"));
|
||||
item.put("multiheight", set.getString("multiheight"));
|
||||
|
||||
// description may not exist in all schemas, handle gracefully
|
||||
try {
|
||||
item.put("description", set.getString("description"));
|
||||
} catch (SQLException e) {
|
||||
item.put("description", "");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a catalog item reference from a result set that joined
|
||||
* catalog_items with catalog_pages.
|
||||
*/
|
||||
public static Map<String, Object> readCatalogRef(ResultSet set) throws SQLException {
|
||||
Map<String, Object> ref = new HashMap<>();
|
||||
ref.put("id", set.getInt("ci_id"));
|
||||
ref.put("catalog_name", set.getString("catalog_name"));
|
||||
ref.put("cost_credits", set.getInt("cost_credits"));
|
||||
ref.put("cost_points", set.getInt("cost_points"));
|
||||
ref.put("points_type", set.getInt("points_type"));
|
||||
ref.put("page_id", set.getInt("ci_page_id"));
|
||||
ref.put("page_caption", set.getString("page_caption"));
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist of allowed field names for update operations.
|
||||
* Prevents SQL injection via arbitrary column names.
|
||||
*/
|
||||
public static final java.util.Set<String> ALLOWED_UPDATE_FIELDS = java.util.Set.of(
|
||||
"item_name", "public_name", "sprite_id", "type", "width", "length",
|
||||
"stack_height", "allow_stack", "allow_walk", "allow_sit", "allow_lay",
|
||||
"allow_gift", "allow_trade", "allow_recycle", "allow_marketplace_sell",
|
||||
"allow_inventory_stack", "interaction_type", "interaction_modes_count",
|
||||
"vending_ids", "customparams", "effect_id_male", "effect_id_female",
|
||||
"clothing_on_walk", "multiheight", "description"
|
||||
);
|
||||
|
||||
/**
|
||||
* Map camelCase JS field names to DB column names.
|
||||
*/
|
||||
public static final Map<String, String> FIELD_MAP = Map.ofEntries(
|
||||
Map.entry("itemName", "item_name"),
|
||||
Map.entry("publicName", "public_name"),
|
||||
Map.entry("spriteId", "sprite_id"),
|
||||
Map.entry("type", "type"),
|
||||
Map.entry("width", "width"),
|
||||
Map.entry("length", "length"),
|
||||
Map.entry("stackHeight", "stack_height"),
|
||||
Map.entry("allowStack", "allow_stack"),
|
||||
Map.entry("allowWalk", "allow_walk"),
|
||||
Map.entry("allowSit", "allow_sit"),
|
||||
Map.entry("allowLay", "allow_lay"),
|
||||
Map.entry("allowGift", "allow_gift"),
|
||||
Map.entry("allowTrade", "allow_trade"),
|
||||
Map.entry("allowRecycle", "allow_recycle"),
|
||||
Map.entry("allowMarketplaceSell", "allow_marketplace_sell"),
|
||||
Map.entry("allowInventoryStack", "allow_inventory_stack"),
|
||||
Map.entry("interactionType", "interaction_type"),
|
||||
Map.entry("interactionModesCount", "interaction_modes_count"),
|
||||
Map.entry("vendingIds", "vending_ids"),
|
||||
Map.entry("customparams", "customparams"),
|
||||
Map.entry("effectIdMale", "effect_id_male"),
|
||||
Map.entry("effectIdFemale", "effect_id_female"),
|
||||
Map.entry("clothingOnWalk", "clothing_on_walk"),
|
||||
Map.entry("multiheight", "multiheight"),
|
||||
Map.entry("description", "description")
|
||||
);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
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.furnieditor.FurniEditorInteractionsComposer;
|
||||
import com.eu.habbo.messages.outgoing.furnieditor.FurniEditorResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class FurniEditorInteractionsEvent extends MessageHandler {
|
||||
|
||||
private static List<String> cachedInteractions = null;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedInteractions == null) {
|
||||
synchronized (FurniEditorInteractionsEvent.class) {
|
||||
if (cachedInteractions == null) {
|
||||
List<String> list = new ArrayList<>();
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
Statement stmt = connection.createStatement();
|
||||
ResultSet set = stmt.executeQuery("SELECT DISTINCT interaction_type FROM items_base WHERE interaction_type != '' ORDER BY interaction_type ASC")) {
|
||||
while (set.next()) {
|
||||
list.add(set.getString("interaction_type"));
|
||||
}
|
||||
}
|
||||
cachedInteractions = Collections.unmodifiableList(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.client.sendResponse(new FurniEditorInteractionsComposer(cachedInteractions));
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
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.furnieditor.FurniEditorResultComposer;
|
||||
import com.eu.habbo.messages.outgoing.furnieditor.FurniEditorSearchComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FurniEditorSearchEvent extends MessageHandler {
|
||||
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
String query = this.packet.readString();
|
||||
String type = this.packet.readString();
|
||||
int page = this.packet.readInt();
|
||||
|
||||
// Input validation
|
||||
if (query.length() > 100) {
|
||||
query = query.substring(0, 100);
|
||||
}
|
||||
|
||||
if (page < 1) page = 1;
|
||||
|
||||
int offset = (page - 1) * PAGE_SIZE;
|
||||
|
||||
// Build WHERE clause
|
||||
StringBuilder whereClause = new StringBuilder("WHERE 1=1");
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (!query.isEmpty()) {
|
||||
// Try numeric match first (id or sprite_id)
|
||||
boolean isNumeric = false;
|
||||
try {
|
||||
int numericQuery = Integer.parseInt(query);
|
||||
isNumeric = true;
|
||||
whereClause.append(" AND (id = ? OR sprite_id = ? OR item_name LIKE ? OR public_name LIKE ?)");
|
||||
params.add(numericQuery);
|
||||
params.add(numericQuery);
|
||||
params.add("%" + query + "%");
|
||||
params.add("%" + query + "%");
|
||||
} catch (NumberFormatException e) {
|
||||
whereClause.append(" AND (item_name LIKE ? OR public_name LIKE ?)");
|
||||
params.add("%" + query + "%");
|
||||
params.add("%" + query + "%");
|
||||
}
|
||||
}
|
||||
|
||||
if (type != null && !type.isEmpty()) {
|
||||
whereClause.append(" AND type = ?");
|
||||
params.add(type);
|
||||
}
|
||||
|
||||
// Count total
|
||||
int total = 0;
|
||||
String countSql = "SELECT COUNT(*) FROM items_base " + whereClause;
|
||||
String dataSql = "SELECT * FROM items_base " + whereClause + " ORDER BY id ASC LIMIT ? OFFSET ?";
|
||||
|
||||
List<Map<String, Object>> items = new ArrayList<>();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
// Get total count
|
||||
try (PreparedStatement stmt = connection.prepareStatement(countSql)) {
|
||||
int idx = 1;
|
||||
for (Object param : params) {
|
||||
if (param instanceof Integer) {
|
||||
stmt.setInt(idx++, (Integer) param);
|
||||
} else {
|
||||
stmt.setString(idx++, (String) param);
|
||||
}
|
||||
}
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
total = rs.getInt(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get items page
|
||||
try (PreparedStatement stmt = connection.prepareStatement(dataSql)) {
|
||||
int idx = 1;
|
||||
for (Object param : params) {
|
||||
if (param instanceof Integer) {
|
||||
stmt.setInt(idx++, (Integer) param);
|
||||
} else {
|
||||
stmt.setString(idx++, (String) param);
|
||||
}
|
||||
}
|
||||
stmt.setInt(idx++, PAGE_SIZE);
|
||||
stmt.setInt(idx, offset);
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
items.add(FurniEditorHelper.readBaseItem(rs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.client.sendResponse(new FurniEditorSearchComposer(items, total, page));
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.eu.habbo.messages.incoming.furnieditor;
|
||||
|
||||
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.furnieditor.FurniEditorResultComposer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FurniEditorUpdateEvent extends MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = this.packet.readInt();
|
||||
String jsonFieldsStr = this.packet.readString();
|
||||
|
||||
if (id <= 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "Invalid item ID"));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject json;
|
||||
try {
|
||||
json = JsonParser.parseString(jsonFieldsStr).getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "Invalid JSON data"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.size() == 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No fields to update"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build dynamic UPDATE with whitelisted fields
|
||||
StringBuilder setClauses = new StringBuilder();
|
||||
List<Object> values = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
|
||||
String jsKey = entry.getKey();
|
||||
String dbColumn = FurniEditorHelper.FIELD_MAP.get(jsKey);
|
||||
|
||||
if (dbColumn == null || !FurniEditorHelper.ALLOWED_UPDATE_FIELDS.contains(dbColumn)) {
|
||||
continue; // Skip unknown or disallowed fields
|
||||
}
|
||||
|
||||
if (setClauses.length() > 0) setClauses.append(", ");
|
||||
setClauses.append("`").append(dbColumn).append("` = ?");
|
||||
|
||||
JsonElement val = entry.getValue();
|
||||
if (val.isJsonPrimitive()) {
|
||||
if (val.getAsJsonPrimitive().isBoolean()) {
|
||||
values.add(val.getAsBoolean() ? "1" : "0");
|
||||
} else if (val.getAsJsonPrimitive().isNumber()) {
|
||||
// Check if it's a decimal number
|
||||
String numStr = val.getAsString();
|
||||
if (numStr.contains(".")) {
|
||||
values.add(val.getAsDouble());
|
||||
} else {
|
||||
values.add(val.getAsInt());
|
||||
}
|
||||
} else {
|
||||
values.add(val.getAsString());
|
||||
}
|
||||
} else {
|
||||
values.add(val.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (setClauses.length() == 0) {
|
||||
this.client.sendResponse(new FurniEditorResultComposer(false, "No valid fields to update"));
|
||||
return;
|
||||
}
|
||||
|
||||
String sql = "UPDATE items_base SET " + setClauses + " WHERE id = ?";
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
int idx = 1;
|
||||
for (Object value : values) {
|
||||
if (value instanceof Integer) {
|
||||
stmt.setInt(idx++, (Integer) value);
|
||||
} else if (value instanceof Double) {
|
||||
stmt.setDouble(idx++, (Double) value);
|
||||
} else {
|
||||
stmt.setString(idx++, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
stmt.setInt(idx, id);
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
|
||||
// Reload emulator item definitions
|
||||
Emulator.getGameEnvironment().getItemManager().loadItems();
|
||||
|
||||
this.client.sendResponse(new FurniEditorResultComposer(true, "Item updated", id));
|
||||
}
|
||||
}
|
||||
@@ -556,6 +556,12 @@ public class Outgoing {
|
||||
public static final int SnowStormUserRematchedComposer = 5029;
|
||||
|
||||
|
||||
// Furni Editor
|
||||
public static final int FurniEditorSearchComposer = 10040;
|
||||
public static final int FurniEditorDetailComposer = 10041;
|
||||
public static final int FurniEditorInteractionsComposer = 10043;
|
||||
public static final int FurniEditorResultComposer = 10044;
|
||||
|
||||
// Catalog Admin
|
||||
public static final int CatalogAdminResultComposer = 10059;
|
||||
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.eu.habbo.messages.outgoing.furnieditor;
|
||||
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FurniEditorDetailComposer extends MessageComposer {
|
||||
private final Map<String, Object> item;
|
||||
private final int usageCount;
|
||||
private final List<Map<String, Object>> catalogItems;
|
||||
private final String furniDataJson;
|
||||
|
||||
public FurniEditorDetailComposer(Map<String, Object> item, int usageCount, List<Map<String, Object>> catalogItems, String furniDataJson) {
|
||||
this.item = item;
|
||||
this.usageCount = usageCount;
|
||||
this.catalogItems = catalogItems;
|
||||
this.furniDataJson = furniDataJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.FurniEditorDetailComposer);
|
||||
|
||||
// 14 base fields
|
||||
this.response.appendInt((int) item.get("id"));
|
||||
this.response.appendInt((int) item.get("sprite_id"));
|
||||
this.response.appendString((String) item.getOrDefault("item_name", ""));
|
||||
this.response.appendString((String) item.getOrDefault("public_name", ""));
|
||||
this.response.appendString((String) item.getOrDefault("type", "s"));
|
||||
this.response.appendInt((int) item.getOrDefault("width", 1));
|
||||
this.response.appendInt((int) item.getOrDefault("length", 1));
|
||||
this.response.appendDouble((double) item.getOrDefault("stack_height", 0.0));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_stack", "1"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_walk", "0"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_sit", "0"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_lay", "0"))));
|
||||
this.response.appendString((String) item.getOrDefault("interaction_type", ""));
|
||||
this.response.appendInt((int) item.getOrDefault("interaction_modes_count", 0));
|
||||
|
||||
// 13 extended fields
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_gift", "1"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_trade", "1"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_recycle", "1"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_marketplace_sell", "1"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_inventory_stack", "1"))));
|
||||
this.response.appendString((String) item.getOrDefault("vending_ids", ""));
|
||||
this.response.appendString((String) item.getOrDefault("customparams", ""));
|
||||
this.response.appendInt((int) item.getOrDefault("effect_id_male", 0));
|
||||
this.response.appendInt((int) item.getOrDefault("effect_id_female", 0));
|
||||
this.response.appendString((String) item.getOrDefault("clothing_on_walk", ""));
|
||||
this.response.appendString((String) item.getOrDefault("multiheight", ""));
|
||||
this.response.appendString((String) item.getOrDefault("description", ""));
|
||||
|
||||
// usage count
|
||||
this.response.appendInt(this.usageCount);
|
||||
|
||||
// catalog references
|
||||
this.response.appendInt(this.catalogItems.size());
|
||||
for (Map<String, Object> ci : this.catalogItems) {
|
||||
this.response.appendInt((int) ci.get("id"));
|
||||
this.response.appendString((String) ci.getOrDefault("catalog_name", ""));
|
||||
this.response.appendInt((int) ci.getOrDefault("cost_credits", 0));
|
||||
this.response.appendInt((int) ci.getOrDefault("cost_points", 0));
|
||||
this.response.appendInt((int) ci.getOrDefault("points_type", 0));
|
||||
this.response.appendInt((int) ci.getOrDefault("page_id", -1));
|
||||
this.response.appendString((String) ci.getOrDefault("page_caption", ""));
|
||||
}
|
||||
|
||||
// furnidata JSON string
|
||||
this.response.appendString(this.furniDataJson != null ? this.furniDataJson : "{}");
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.eu.habbo.messages.outgoing.furnieditor;
|
||||
|
||||
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 FurniEditorInteractionsComposer extends MessageComposer {
|
||||
private final List<String> interactions;
|
||||
|
||||
public FurniEditorInteractionsComposer(List<String> interactions) {
|
||||
this.interactions = interactions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.FurniEditorInteractionsComposer);
|
||||
this.response.appendInt(this.interactions.size());
|
||||
|
||||
for (String interaction : this.interactions) {
|
||||
this.response.appendString(interaction);
|
||||
}
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.eu.habbo.messages.outgoing.furnieditor;
|
||||
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
public class FurniEditorResultComposer extends MessageComposer {
|
||||
private final boolean success;
|
||||
private final String message;
|
||||
private final int id;
|
||||
|
||||
public FurniEditorResultComposer(boolean success, String message) {
|
||||
this(success, message, -1);
|
||||
}
|
||||
|
||||
public FurniEditorResultComposer(boolean success, String message, int id) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.FurniEditorResultComposer);
|
||||
this.response.appendBoolean(this.success);
|
||||
this.response.appendString(this.message);
|
||||
this.response.appendInt(this.id);
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.eu.habbo.messages.outgoing.furnieditor;
|
||||
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FurniEditorSearchComposer extends MessageComposer {
|
||||
private final List<Map<String, Object>> items;
|
||||
private final int total;
|
||||
private final int page;
|
||||
|
||||
public FurniEditorSearchComposer(List<Map<String, Object>> items, int total, int page) {
|
||||
this.items = items;
|
||||
this.total = total;
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.FurniEditorSearchComposer);
|
||||
this.response.appendInt(this.items.size());
|
||||
|
||||
for (Map<String, Object> item : this.items) {
|
||||
this.response.appendInt((int) item.get("id"));
|
||||
this.response.appendInt((int) item.get("sprite_id"));
|
||||
this.response.appendString((String) item.getOrDefault("item_name", ""));
|
||||
this.response.appendString((String) item.getOrDefault("public_name", ""));
|
||||
this.response.appendString((String) item.getOrDefault("type", "s"));
|
||||
this.response.appendInt((int) item.getOrDefault("width", 1));
|
||||
this.response.appendInt((int) item.getOrDefault("length", 1));
|
||||
this.response.appendDouble((double) item.getOrDefault("stack_height", 0.0));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_stack", "1"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_walk", "0"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_sit", "0"))));
|
||||
this.response.appendBoolean("1".equals(String.valueOf(item.getOrDefault("allow_lay", "0"))));
|
||||
this.response.appendString((String) item.getOrDefault("interaction_type", ""));
|
||||
this.response.appendInt((int) item.getOrDefault("interaction_modes_count", 0));
|
||||
}
|
||||
|
||||
this.response.appendInt(this.total);
|
||||
this.response.appendInt(this.page);
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user